from collections import OrderedDict
from typing import Any
from typing import Dict
from typing import List

__contracts__ = ["resource"]


async def present(
    hub,
    ctx,
    name: str,
    description: str = "",
    key_usage: str = "ENCRYPT_DECRYPT",
    key_spec: str = "SYMMETRIC_DEFAULT",
    key_state: str = "Enabled",
    origin: str = "AWS_KMS",
    multi_region: bool = False,
    policy: str = None,
    bypass_policy_lockout_safety_check: bool = False,
    tags: List = None,
) -> Dict[str, Any]:
    r"""
    **Autogenerated function**

    Create or update AWS kms key.

    Update limitations:
    Tags can be updated o the key using tag_resource and untag_resource.
    Key state can be updated: disable / enable key.
    Policy cannot be updated on the key.

    Args:
        hub:
        ctx:
        name(str): A name, ID to identify the resource.
        description(str, Optional): description of the key
        key_usage(str, Default: 'ENCRYPT_DECRYPT'): optional values: 'SIGN_VERIFY'|'ENCRYPT_DECRYPT'
        key_spec(str, Default: "SYMMETRIC_DEFAULT"): optional values: 'RSA_2048'|'RSA_3072'|
            'RSA_4096'|'ECC_NIST_P256'|'ECC_NIST_P384'|'ECC_NIST_P521'|'ECC_SECG_P256K1'|'SYMMETRIC_DEFAULT'
        key_state(str: Default: Enabled): by default key is enabled. Use 'Disabled' to disable the key.
        origin(str, Default: "AWS_KMS"): optional values: 'AWS_KMS'|'EXTERNAL'|'AWS_CLOUDHSM'
        multi_region(bool, Default: False):
        policy(str, Optional): a default policy is created for each key
        bypass_policy_lockout_safety_check(bool, Default = False): bypass policy safety check, should be true when
            policy is specified or key creation fails with this error:
            "The new key policy will not allow you to update the key policy in the future.
            This field is not returned by 'describe'.
        tags(List, Optional): List of TagKey and TagValue pairs

    Request Syntax:
        [key-resource-id]:
          aws.kms.key.present:
          - description: 'string'
          - key_state: 'string'
          - key_usage: 'string'
          - key_spec: 'string'
          - multi_region: 'boolean'
          - bypass_policy_lockout_safety_check: 'boolean'
          - policy: 'string'
          - tags:
            - TagKey: 'string'
              TagValue: 'string'

    Returns:
        Dict[str, Any]

    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: sls

            new-key:
                aws.kms.key.present:
                    - key_state: Enabled
                    - description: key-with-policy-and-tags
                    - key_usage: ENCRYPT_DECRYPT
                    - key_spec: SYMMETRIC_DEFAULT
                    - multi_region: false
                    - bypass_policy_lockout_safety_check: true
                    - policy: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"EnableIAMUserPermissions\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::537227425989:root\"},\"Action\":[\"kms:Create*\",\"kms:Describe*\",\"kms:Enable*\"],\"Resource\":\"*\"}]}"
                    - tags:
                        - TagKey: test-key
                          TagValue: test-value
                        - TagKey: test-key-1
                          TagValue: test-key-1
    """

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

    before = await hub.exec.boto3.client.kms.describe_key(ctx, KeyId=name)
    if ctx.get("test", False):
        if before and before["result"] is True:
            result["comment"] = f"Would update aws.kms.key {name}"
            result["result"] = True
        else:
            result["comment"] = f"Would create aws.kms.key {name}"
            result["result"] = True
        return result

    if before["result"]:
        result["old_state"] = before["ret"].get("KeyMetadata", None)
        result["comment"] = f"aws.kms.key '{name}' already exists. "

        # Update key tags if tags are specified
        if tags is not None:
            key_tags = await hub.exec.boto3.client.kms.list_resource_tags(
                ctx, KeyId=name
            )
            if key_tags and key_tags["result"] is True:
                if tags != key_tags["ret"].get("Tags", []):
                    hub.log.debug(f"aws.kms.key '{name}' tags update")
                    update_ret = await hub.exec.aws.kms.key.update_key_tags(
                        ctx=ctx,
                        key_id=name,
                        old_tags=key_tags["ret"].get("Tags", []),
                        new_tags=tags,
                    )

                    result["result"] = update_ret["result"]
                    if not result["result"]:
                        result["comment"] = result["comment"] + str(
                            update_ret["comment"]
                        )
                        return result

                    result["comment"] = (
                        result["comment"] + f"Updated tags on aws.kms.key '{name}'. "
                    )

        # Enable/Disable key
        # No updates for key_state "PendingDeletion", which means the key is scheduled to be deleted.
        old_state = result["old_state"].get("KeyState", "")
        if key_state != old_state:
            update_ret = None
            if old_state == "Enabled" and key_state == "Disabled":
                update_ret = await hub.exec.boto3.client.kms.disable_key(
                    ctx, KeyId=name
                )
            elif old_state == "Disabled" and key_state == "Enabled":
                update_ret = await hub.exec.boto3.client.kms.enable_key(ctx, KeyId=name)

            if update_ret:
                hub.log.debug(
                    f"Updated the state of aws.kms.key '{name}' to '{key_state}'. "
                )
                result["result"] = update_ret["result"]
                if not result["result"]:
                    result["comment"] = result["comment"] + str(update_ret["comment"])
                    return result
                result["comment"] = (
                    result["comment"]
                    + f"Update aws.kms.key '{name}' state to {key_state}. "
                )
    else:
        try:
            ret = await hub.exec.boto3.client.kms.create_key(
                ctx,
                Description=description,
                KeyUsage=key_usage,
                KeySpec=key_spec,
                Origin=origin,
                MultiRegion=multi_region,
                Policy=policy,
                BypassPolicyLockoutSafetyCheck=bypass_policy_lockout_safety_check,
                Tags=tags,
            )

            result["result"] = ret["result"]
            if not result["result"]:
                result["comment"] = ret["comment"]
                return result
            name = ret["ret"]["KeyMetadata"]["KeyId"]
            result[
                "comment"
            ] = f"Created aws.kms.key '{name}' with description '{description}'. "
        except hub.tool.boto3.exception.ClientError as e:
            result["comment"] = f"{e.__class__.__name__}: {e}"
            result["result"] = False

    try:
        after = await hub.exec.boto3.client.kms.describe_key(ctx, KeyId=name)
        if after.get("ret"):
            result["new_state"] = after["ret"].get("KeyMetadata", None)
    except Exception as e:
        result["comment"] = str(e)
        result["result"] = False
    return result


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

    Key cannot be immediately deleted but can be scheduled to be deleted.
    Also key can be disabled using the present function with key_state = 'Disabled'.
    Args:
        hub:
        ctx:
        name(Text): A name, ID of the key.
        pending_window_in_days(int, Optional, Default: 7 days): -- how many days before key is deleted.
    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: sls

            resource_is_absent:
              aws.kms.key.absent:
                - name: value
    """

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

    before = await hub.exec.boto3.client.kms.describe_key(ctx, KeyId=name)

    if not before:
        result["comment"] = f"aws.kms.key '{name}' already absent"
        return result
    elif before["ret"]["KeyMetadata"].get("DeletionDate", None):
        result["comment"] = f"aws.kms.key '{name}' already scheduled to be deleted"
        return result
    elif ctx.get("test", False):
        result["comment"] = f"Would schedule deletion of aws.kms.key '{name}'"
        return result
    else:
        try:
            result["old_state"] = before["ret"].get("KeyMetadata", None)
            # Minimum deletion schedule 7 days (from 7 - 30)
            ret = await hub.exec.boto3.client.kms.schedule_key_deletion(
                ctx, KeyId=name, PendingWindowInDays=pending_window_in_days
            )
            if not before:
                result["result"] = ret["result"]
            if not result["result"]:
                result["comment"] = ret["comment"]
                result["result"] = False
                return result
            result[
                "comment"
            ] = f"aws.kms.key '{name}' is scheduled for deletion in {pending_window_in_days} days"
        except hub.tool.boto3.exception.ClientError as e:
            result["comment"] = f"{e.__class__.__name__}: {e}"

    try:
        after = await hub.exec.boto3.client.kms.describe_key(ctx, KeyId=name)
        if after.get("ret"):
            result["new_state"] = after["ret"].get("KeyMetadata", None)
    except Exception as e:
        result["comment"] = str(e)
        result["result"] = False
    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 keys in the caller's Amazon Web Services account and region. For more information about
    keys, see Createkey. By default, the Listkeys operation returns all keys in the account and region.
    To get only the keys associated with a particular KMS key, use the KeyId parameter. The Listkeys response
    can include keys that you created and associated with your customer managed keys, and keys that Amazon Web
    Services created and associated with Amazon Web Services managed keys in your account. You can recognize Amazon
    Web Services keys because their names have the format aws/<service-name>, such as aws/dynamodb. The response
    might also include keys that have no TargetKeyId field. These are predefined keys that Amazon Web Services
    has created but has not yet associated with a KMS key. keys that Amazon Web Services creates in your account,
    including predefined keys, do not count against your KMS keys quota.  Cross-account use: No. Listkeys
    does not return keys in other Amazon Web Services accounts.  Required permissions: kms:Listkeys (IAM
    policy) For details, see Controlling access to keys in the Key Management Service Developer Guide.  Related
    operations:     CreateKey


    Returns:
        Dict[str, Any]

    Examples:

        .. code-block:: bash

            $ idem describe aws.kms.key
    """

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

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

    # Arn is not used for present but required for arg binding
    describe_parameters = OrderedDict(
        {
            "KeyId": "key_id",
            "Arn": "arn",
            "KeyState": "key_state",
            "Description": "description",
            "KeyUsage": "key_usage",
            "KeySpec": "key_spec",
            "MultiRegion": "multi_region",
            "Policy": "policy",
            "Tags": "tags",
        }
    )

    for key in ret["ret"]["Keys"]:
        resource = {}
        # Get key details to match the 'present' function parameters
        key_details = await hub.exec.boto3.client.kms.describe_key(
            ctx, KeyId=key["KeyId"]
        )
        if key_details and key_details["result"] is True:
            resource = key_details["ret"].get("KeyMetadata", {})

        # Get key tags
        key_tags = await hub.exec.boto3.client.kms.list_resource_tags(
            ctx, KeyId=key["KeyId"]
        )
        if key_tags and key_tags["result"] is True:
            resource["Tags"] = key_tags["ret"].get("Tags", [])

        # Get key policy
        key_policy = await hub.exec.boto3.client.kms.get_key_policy(
            ctx, KeyId=key["KeyId"], PolicyName="default"
        )
        if (
            key_policy
            and key_policy["result"] is True
            and key_policy["ret"].get("Policy", None)
        ):
            resource["Policy"] = key_policy["ret"].get("Policy")

        translated_resource = []
        for camel_case_key, snake_case_key in describe_parameters.items():
            if resource.get(camel_case_key) is not None:
                translated_resource.append(
                    {snake_case_key: resource.get(camel_case_key)}
                )

        result[key["KeyId"]] = {"aws.kms.key.present": translated_resource}

    return result
