import copy
from typing import Any
from typing import Dict

__contracts__ = ["resource"]

TREQ = {
    "absent": {
        "require": [
            "aws.kms.key.absent",
        ],
    },
    "present": {
        "require": [
            "aws.kms.key.present",
        ],
    },
}


async def present(
    hub, ctx, name: str, target_key_id: str, resource_id: str = None
) -> Dict[str, Any]:
    r"""
    **Autogenerated function**

    Creates a friendly name for a KMS key.

    You can use an alias to identify a KMS key in the KMS console, in the DescribeKey operation and in cryptographic
    operations ,such as Encrypt and GenerateDataKey . You can also change the KMS key that's associated with the alias
    ( UpdateAlias ) or delete the alias ( DeleteAlias ) at any time. These operations don't affect the underlying KMS key.

    You can associate the alias with any customer managed key in the same Amazon Web Services Region.
    Each alias is associated with only one KMS key at a time, but a KMS key can have multiple aliases.
    A valid KMS key is required. You can't create an alias without a KMS key.

    The alias must be unique in the account and Region, but you can have aliases with the same name in different Regions

    Args:
        hub:
        ctx:
        name(Text): Specifies the alias name. This value must begin with alias/ followed by a name, such as alias/ExampleAlias.
                   The AliasName value must be string of 1-256 characters. It can contain only alphanumeric characters,
                   forward slashes (/), underscores (_), and dashes (-). The alias name cannot begin with alias/aws/ .
                   The alias/aws/ prefix is reserved for Amazon Web Services managed keys .
        target_key_id(Text): Associates the alias with the specified customer managed key . The KMS key must be in the same Amazon Web Services Region.
                            A valid key ID is required. If you supply a null or empty string value, this operation returns an error.
        resource_id(Text, optional): alias name used to identify a KMS key

    Request Syntax:
        [kms-alias-name]:
          aws.kms.alias.present:
          - target_key_id: 'string'
          - resource_id: 'string'

    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: sls

            alias/eks_cluster_key:
                aws.kms.alias.present:
                    - target_key_id: 1234abcd-12ab-34cd-56ef-1234567890ab
                    - resource_id: alias/eks_cluster_key
    """

    result = dict(comment="", old_state=None, new_state=None, name=name, result=True)
    # check if alias name already exists
    before = await hub.exec.aws.kms.alias.get_alias_by_name(
        ctx, resource_id if resource_id else name
    )
    is_alias_applied = False

    if not before["result"]:
        result["result"] = False
        result["comment"] = before["comment"]
        return result
    elif before["ret"]:
        result[
            "old_state"
        ] = hub.tool.aws.kms.conversion_utils.convert_raw_key_alias_to_present(
            raw_resource=before["ret"]
        )
        plan_state = copy.deepcopy(result["old_state"])
        # alias already exists with that name. Update alias if target key is different
        if result["old_state"].get("target_key_id") != target_key_id:
            try:
                if not ctx.get("test", False):
                    update_ret = await hub.exec.boto3.client.kms.update_alias(
                        ctx, AliasName=name, TargetKeyId=target_key_id
                    )
                    result["result"] = update_ret["result"]
                    if not result["result"]:
                        result["comment"] = update_ret["comment"]
                        return result
                    result[
                        "comment"
                    ] = f"Updated alias of KMS key {target_key_id} to {name}"
                    is_alias_applied = True
                else:
                    result["comment"] = f"Would update aws.kms.alias {name}"
                    plan_state["target_key_id"] = target_key_id

            except hub.tool.boto3.exception.ClientError as e:
                result["comment"] = f"{e.__class__.__name__}: {e}"
                result["result"] = False
                return result
        else:
            result[
                "comment"
            ] = f"KMS Alias '{name}' is already associated with the KMS target key '{target_key_id}'"
    else:
        # No alias exists with that name. create new alias

        if ctx.get("test", False):
            result["new_state"] = hub.tool.aws.test_state_utils.generate_test_state(
                enforced_state={},
                desired_state={
                    "name": name,
                    "target_key_id": target_key_id,
                },
            )
            result["comment"] = f"Would create aws.kms.alias {name}"
            return result

        try:
            ret = await hub.exec.boto3.client.kms.create_alias(
                ctx, AliasName=name, TargetKeyId=target_key_id
            )
            result["result"] = ret["result"]
            if not result["result"]:
                result["comment"] = ret["comment"]
                return result
            result[
                "comment"
            ] = f"Created aws.kms.alias {name} for KMS key aws.kms.key {target_key_id}"
            is_alias_applied = True
        except hub.tool.boto3.exception.ClientError as e:
            result["comment"] = f"{e.__class__.__name__}: {e}"
            result["result"] = False
            return result
    try:
        if ctx.get("test", False):
            result["new_state"] = plan_state
        # get new state only if we create or modify alias
        elif is_alias_applied:
            after = await hub.exec.aws.kms.alias.get_alias_by_name(ctx, name)
            if after["result"] and after.get("ret"):
                result[
                    "new_state"
                ] = hub.tool.aws.kms.conversion_utils.convert_raw_key_alias_to_present(
                    raw_resource=after["ret"]
                )
            else:
                result["comment"] = after["comment"]
                result["result"] = False
        else:
            result["new_state"] = copy.deepcopy(result["old_state"])
    except Exception as e:
        result["comment"] = str(e)
        result["result"] = False
    return result


async def absent(hub, ctx, name: str, resource_id: str = None) -> Dict[str, Any]:
    r"""
    **Autogenerated function**
    Deletes the specified alias.
    Because an alias is not a property of a KMS key, you can delete and change the aliases of a KMS key without
    affecting the KMS key. Also, aliases do not appear in the response from the DescribeKey operation.
    To get the aliases of all KMS keys, use the ListAliases operation.

    Each KMS key can have multiple aliases. To change the alias of a KMS key, use DeleteAlias to delete the current
    alias and CreateAlias to create a new alias. To associate an existing alias with a different KMS key,
    call UpdateAlias .

    Args:
        hub:
        ctx:
        name(Text): Alias name
        resource_id(Text, optional): ID of the KMS alias.

    Returns:
        Dict[str, Any]

    Request Syntax:
        [kms-alias-name]:
          aws.kms.alias.absent:
            - name: string
            - resource_id: string

    Examples:

        .. code-block:: sls

            alias/eks_cluster_key:
              aws.kms.alias.absent:
                - name: alias/eks_cluster_key
                - resource_id: alias/eks_cluster_key
    """

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

    before = await hub.exec.aws.kms.alias.get_alias_by_name(
        ctx, (resource_id if resource_id else name)
    )
    if not before["result"]:
        result["result"] = False
        result["comment"] = before["comment"]
        return result

    if not before["ret"]:
        result["comment"] = f"aws.kms.policy '{name}' already absent"
    elif ctx.get("test", False):
        result[
            "old_state"
        ] = hub.tool.aws.kms.conversion_utils.convert_raw_key_alias_to_present(
            raw_resource=before["ret"]
        )
        result["comment"] = f"Would delete aws.kms.alias '{name}'"
    else:
        result[
            "old_state"
        ] = hub.tool.aws.kms.conversion_utils.convert_raw_key_alias_to_present(
            raw_resource=before["ret"]
        )
        try:
            ret = await hub.exec.boto3.client.kms.delete_alias(
                ctx, AliasName=(resource_id if resource_id else name)
            )
            result["result"] = ret["result"]
            if not result["result"]:
                result["comment"] = ret["comment"]
                result["result"] = False
                return result
            result["comment"] = f"aws.kms.alias '{name}' is deleted"
        except hub.tool.boto3.exception.ClientError as e:
            result["comment"] = f"{e.__class__.__name__}: {e}"

    return result


async def describe(hub, ctx) -> Dict[str, Dict[str, Any]]:
    r"""
    Describe the resource in a way that can be recreated/managed with the corresponding "present" function


    Gets a list of aliases in the caller's Amazon Web Services account and region.
    By default, the ListAliases operation returns all aliases in the account and region.
    To get only the aliases associated with a particular KMS key, use the KeyId parameter.
    The ListAliases response can include aliases that you created and associated with your customer managed keys,
    and aliases that Amazon Web Services created and associated with Amazon Web Services managed keys in your account.
    You can recognize Amazon Web Services aliases because their names have the format aws/<service-name> ,
    such as aws/dynamodb .

    The response might also include aliases that have no TargetKeyId field.
    These are predefined aliases that Amazon Web Services has created but has not yet associated with a KMS key.
    Aliases that Amazon Web Services creates in your account, including predefined aliases,
    do not count against your KMS aliases quota .


    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: bash

            $ idem describe aws.kms.alias
    """

    result = {}
    ret = await hub.exec.boto3.client.kms.list_aliases(ctx)

    if not ret["result"]:
        hub.log.debug(f"Could not describe aws.kms.alias {ret['comment']}")
        return {}

    for key in ret["ret"]["Aliases"]:
        # Remove AWS reserved aliases as we cannot modify them using present/absent
        if key.get("AliasName") is not None and "alias/aws/" in key.get("AliasName"):
            continue
        # Get alias details to match the 'present' function parameters
        translated_resource = (
            hub.tool.aws.kms.conversion_utils.convert_raw_key_alias_to_present(
                raw_resource=key
            )
        )
        result[translated_resource["resource_id"]] = {
            "aws.kms.alias.present": [
                {parameter_key: parameter_value}
                for parameter_key, parameter_value in translated_resource.items()
            ]
        }

    return result
