"""
Autogenerated using `pop-create-idem <https://gitlab.com/saltstack/pop/pop-create-idem>`__

hub.exec.boto3.client.organizations.deregister_delegated_administrator
hub.exec.boto3.client.organizations.list_delegated_administrators
hub.exec.boto3.client.organizations.register_delegated_administrator
"""
import copy
from collections import OrderedDict
from typing import Any
from typing import Dict
from typing import List

__contracts__ = ["resource"]
SERVICE = "organizations"


TREQ = {
    "absent": {
        "require": [
            "aws.organizations.policy_attachment.absent",
            "aws.organizations.policy.absent",
        ],
    },
    "present": {
        "require": [
            "aws.organizations.organization.present",
            "aws.organizations.organization_unit.present",
        ],
    },
}


async def present(
    hub,
    ctx,
    name: str,
    email: str,
    account_name: str,
    role_name: str,
    iam_user_access_to_billing: str = "ALLOW",
    parent_id: str = None,
    tags: List = None,
) -> Dict[str, Any]:
    r"""
    **Autogenerated function**

    Creates an AWS account that is automatically a member of the organization whose credentials made the request.
    This is an asynchronous request that AWS performs in the background. Because CreateAccount operates asynchronously,
    it can return a successful completion message even though account initialization might still be in progress.
     You might need to wait a few minutes before you can successfully access the account

    Args:
        hub:
        ctx:
        name(Text): A name or ID to identify the resource.
        Email(str, Required): The email address of the owner to assign to the new member account. This email
                              address must not already be associated with another AWS account
        AccountName(str,Required): The friendly name of the member account.
        RoleName(str,Optional): The name of an IAM role that AWS Organizations automatically preconfigures in the
                                new member account. This role trusts the management account, allowing users in the
                                management account to assume the role, as permitted by the management account
                                administrator. The role has administrator permissions in the new member account.
                                If you don't specify this parameter, the role name defaults to OrganizationAccountAccessRole.
        IamUserAccessToBilling(str,Optional,Default:'ALLOW'): If set to ALLOW , the new account enables IAM users to access account
                                                              billing information if they have the required permissions. If set to DENY ,
                                                              only the root user of the new account can access account billing information.
        ParentId(str,Optional): Parent Organizational Unit ID or Root ID for the account. Defaults to the Organization default Root ID
        Tags(list,Optional) : A list of tags that you want to attach to the newly created account

    Request Syntax:
        [account-id]:
          aws.organizations.account.present:
          - email: 'string'
          - account_name: 'string'
          - role_name: 'string'
          - iam_user_access_to_billing: 'string'
          - parent_id: 'string'
          - tags:
            - Key: 'string'
              Value: 'string'

    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: sls

            new-account_id:
                aws.organizations.account.present:
                    - email: xyz@email.com
                    - account_name: name_of_new_account
                    - role_name: role_name1
                    - iam_user_access_to_billing: ALLOW
                    - parent_id : ou108811
                    - tags:
                        - Key: test-key
                          Value: test-value
                        - Key: test-key-1
                          Value: test-key-1
    """

    result = dict(comment="", name=name, result=True, old_state=None, new_state=None)

    before = await hub.exec.boto3.client.organizations.describe_account(
        ctx, AccountId=name
    )

    account_id = None

    if ctx.get("test", False):
        if before:
            result["comment"] = f"Would update aws.organizations.account {name}."
            result["result"] = True
        else:
            result["comment"] = f"Would create aws.oranizations.organization {name}."
            result["result"] = True
        return result

    update_tag = False
    update_parent = False

    if before:
        # Account exists , update
        before["ret"].pop("ResponseMetadata", None)
        result["old_state"] = before["ret"]["Account"]

        account_id = name

        result["comment"] = f"aws.organizations.account {name} already exists . "

        try:

            # we need to list parents to check if move is required in case the new_parent_id is not equal to current_parent_id
            parents = await hub.exec.boto3.client.organizations.list_parents(
                ctx, ChildId=account_id
            )

            if parents:
                current_parent_id = parents["ret"]["Parents"][0]["Id"]

                result["old_state"]["ParentId"] = current_parent_id

                if parent_id is not None and current_parent_id != parent_id:
                    await move_account(hub, ctx, account_id, result, parent_id)
                    update_parent = True

            old_tags = await hub.exec.boto3.client.organizations.list_tags_for_resource(
                ctx, ResourceId=account_id
            )

            if old_tags:
                before["ret"]["Account"]["Tags"] = old_tags["ret"]["Tags"]
                result["old_state"]["Tags"] = old_tags["ret"]["Tags"]

            if tags is not None and old_tags["result"]:

                update_tags_ret = (
                    await hub.exec.aws.organizations.organization.update_tags(
                        ctx, account_id, old_tags["ret"].get("Tags", []), tags
                    )
                )
                if not update_tags_ret["result"]:
                    result[
                        "comment"
                    ] = f" {result['comment']} , {str(update_tags_ret['comment'])}"
                    result["result"] = False
                    return result

                old_comment = result["comment"]

                if update_tags_ret["ret"] is not None:
                    result[
                        "comment"
                    ] = f"'{old_comment}'. Updated tags on aws.organizations.account '{name}'."
                    update_tag = True

            result["result"] = True

        except hub.tool.boto3.exception.ClientError as e:
            result["comment"] = f"{e.__class__.__name__}: {e}"
            result["result"] = False

    else:
        # Account not present , create
        hub.log.debug("Creating member account in the organization")

        try:
            create_account_ret = (
                await hub.exec.boto3.client.organizations.create_account(
                    ctx,
                    Email=email,
                    AccountName=account_name,
                    RoleName=role_name,
                    IamUserAccessToBilling=iam_user_access_to_billing,
                    Tags=tags,
                )
            )

            result["result"] = create_account_ret["result"]
            if not result["result"]:
                result["comment"] = create_account_ret["comment"]
                return result

            account_status_id = create_account_ret["ret"]["CreateAccountStatus"]["Id"]
            account_id = create_account_ret["ret"]["CreateAccountStatus"].get(
                "AccountId", None
            )

            # Call a custom waiter to wait on account's creation.

            acceptors = {
                "SUCCEEDED": "success",
                "FAILED": "failure",
                "IN_PROGRESS": "retry",
            }
            account_waiter = hub.tool.boto3.custom_waiter.waiter_wrapper(
                name="AccountCreated",
                operation="DescribeCreateAccountStatus",
                argument="CreateAccountStatus.State",
                acceptors=acceptors,
                client=hub.tool.boto3.client.get_client(ctx, SERVICE),
            )
            await hub.tool.boto3.client.wait(
                ctx,
                SERVICE,
                "AccountCreated",
                account_waiter,
                CreateAccountRequestId=account_status_id,
            )

            result["comment"] = f"Created aws.organizations.account {name}."

            if account_id is None:
                create_account_status = await hub.exec.boto3.client.organizations.describe_create_account_status(
                    ctx, CreateAccountRequestId=account_status_id
                )
                if not create_account_status:
                    result[
                        "comment"
                    ] = f"{result['comment']} . Could not find account_id for {account_status_id} in aws.organizations.account {name}"
                else:
                    account_id = create_account_status["ret"]["CreateAccountStatus"][
                        "AccountId"
                    ]

            if account_id is not None and parent_id is not None:

                parents = await hub.exec.boto3.client.organizations.list_parents(
                    ctx, ChildId=account_id
                )

                if parents:
                    current_parent_id = parents["ret"]["Parents"][0]["Id"]

                    if current_parent_id != parent_id:
                        await move_account(hub, ctx, account_id, result, parent_id)
            else:
                result[
                    "comment"
                ] = f"{result['comment']} . Could not update parent for aws.organizations.account {name}"
                result["result"] = False
                return result

        except hub.tool.boto3.exception.ClientError as e:
            result["comment"] = f"{e.__class__.__name__}: {e}"
            result["result"] = False

    if not before or update_parent or update_tag:
        try:
            after = await hub.exec.boto3.client.organizations.describe_account(
                ctx, AccountId=account_id
            )
            if after and after.get("ret"):
                after["ret"].pop("ResponseMetadata", None)
                result["new_state"] = after["ret"]["Account"]

                parents = await hub.exec.boto3.client.organizations.list_parents(
                    ctx, ChildId=account_id
                )
                if parents:
                    current_parent_id = parents["ret"]["Parents"][0]["Id"]
                    result["new_state"]["ParentId"] = current_parent_id

                new_tags = (
                    await hub.exec.boto3.client.organizations.list_tags_for_resource(
                        ctx, ResourceId=account_id
                    )
                )
                if new_tags:
                    result["new_state"]["Tags"] = new_tags["ret"]["Tags"]

        except Exception as e:
            result["comment"] = str(e)
            result["result"] = False

    else:
        result["new_state"] = copy.deepcopy(result["old_state"])

    return result


async def absent(
    hub,
    ctx,
    name: str,
) -> Dict[str, Any]:
    r"""
    **Autogenerated function**

    Removes the specified account from the organization.The removed account becomes a standalone
    account that isn't a member of any organization. It's no longer subject to any policies and
    is responsible for its own bill payments. The organization's management account is no longer
    charged for any expenses accrued by the member account after it's removed from the organization.
    This operation can be called only from the organization's management account. Member accounts
    can remove themselves with LeaveOrganization instead.

    Args:
        name(Text): The unique identifier (ID) of the member account that you want
                        to remove from the organization

    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: sls

            [account_id]:
              aws.organizations.organization.absent:
                - name: value
    """

    result = dict(comment="", old_state=None, new_state=None, name=name, result=True)

    account_id = name

    before = await hub.exec.boto3.client.organizations.describe_account(
        ctx, AccountId=account_id
    )

    if ctx.get("test", False):
        if before:
            result["comment"] = f"Would delete aws.organizations.account {name}."
            result["result"] = True
        else:
            result["comment"] = f"aws.organizations.account {name} already absent"
            result["result"] = True
        return result

    if not before:
        result["comment"] = f"aws.organizations.account {name} already absent"
        result["result"] = True
    else:
        before["ret"].pop("ResponseMetadata", None)

        result["old_state"] = before["ret"]["Account"]
        try:
            ret = await hub.exec.boto3.client.organizations.remove_account_from_organization(
                ctx, AccountId=account_id
            )

            result["result"] = ret["result"]

            if not result["result"]:
                result["comment"] = ret["comment"]
                return result
            result["comment"] = f"aws.organizations.account {name} deleted."
        except hub.tool.boto3.exception.ClientError as e:
            result["comment"] = f"{e.__class__.__name__}: {e}"
            result["result"] = False

    return result


async def describe(hub, ctx) -> Dict[str, Dict[str, Any]]:
    r"""
    **Autogenerated function**

    Lists all the accounts in the organization. To request only the accounts in a specified root or organizational
    unit (OU), use the ListAccountsForParent operation instead.


    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: bash

            $ idem describe aws.organizations.account
    """

    result = {}

    describe_ret = await hub.exec.boto3.client.organizations.list_accounts(ctx)
    if not describe_ret:
        hub.log.debug(f"Could not describe account {describe_ret['comment']}")
        return {}

    describe_parameters = OrderedDict(
        {
            "Name": "account_name",
            "Email": "email",
            "ParentId": "parent_id",
            "Tags": "tags",
        }
    )

    accounts = describe_ret["ret"]["Accounts"]

    for account in accounts:
        translated_resource = []
        if account.get("Name") is not None:
            translated_resource.append({"account_name": account.get("Name")})
        if account.get("Email") is not None:
            translated_resource.append({"email": account.get("Email")})

        parent = await hub.exec.boto3.client.organizations.list_parents(
            ctx, ChildId=account["Id"]
        )

        if parent and parent["ret"].get("Parents"):
            translated_resource.append({"parent_id": parent["ret"]["Parents"][0]["Id"]})

        tags = await hub.exec.boto3.client.organizations.list_tags_for_resource(
            ctx, ResourceId=account["Id"]
        )

        if tags and tags["ret"].get("Tags"):
            translated_resource.append({"tags": tags["ret"].get("Tags")})

        result[account["Id"]] = {
            "aws.organizations.account.present": translated_resource
        }
    return result


async def move_account(
    hub, ctx, account_id, result: Dict[str, Any], destination_parent_id
):
    if destination_parent_id is not None:

        parents = await hub.exec.boto3.client.organizations.list_parents(
            ctx, ChildId=account_id
        )

        if parents:
            parent_id = parents["ret"]["Parents"][0]["Id"]

            if parent_id == destination_parent_id:
                result[
                    "comment"
                ] = f"{result['comment']} . aws.organizations.account {account_id} already at {destination_parent_id}"
                return
            else:

                account_move_ret = (
                    await hub.exec.boto3.client.organizations.move_account(
                        ctx,
                        AccountId=account_id,
                        SourceParentId=parent_id,
                        DestinationParentId=destination_parent_id,
                    )
                )
                if not account_move_ret:
                    result[
                        "comment"
                    ] = f"{result['comment']} . Could not move aws.organizations.account {account_id} to {destination_parent_id}"

                    result["result"] = False

                    return
                result[
                    "comment"
                ] = f"{result['comment']} Successfully moved aws.organizations.account {account_id} under {destination_parent_id}"
