"""State module for managing Amazon Config Recorder."""
import copy
from dataclasses import field
from dataclasses import make_dataclass
from typing import Any
from typing import Dict
from typing import List

__contracts__ = ["resource"]


async def present(
    hub,
    ctx,
    name: str,
    role_arn: str,
    recording_group: make_dataclass(
        "ConfigurationRecorder",
        [
            ("allSupported", bool, field(default=None)),
            ("includeGlobalResourceTypes", bool, field(default=None)),
            ("resourceTypes", List[str], field(default=None)),
        ],
    ),
    recording: bool = False,
    resource_id: str = None,
) -> Dict[str, Any]:
    """Creates a new configuration recorder to record the selected resource configurations.

    Args:
        name(str):
            The name of the recorder.

        role_arn (str):
            Amazon Resource Name (ARN) of the IAM role used to describe the Amazon Web Services resources
            associated with the account.

        recording_group (dict[str, Any], Optional):
            Specifies the types of Amazon Web Services resources for which Config records configuration changes.

            * allSupported (bool, Optional):
              Specifies whether Config records configuration changes for every supported type of regional resource.

              If you set this option to true, when Config adds support for a new type of regional resource,
              it starts recording resources of that type automatically.

              If you set this option to true, you cannot enumerate a list of resourceTypes.

            * includeGlobalResourceTypes (bool, Optional):
              Specifies whether Config includes all supported types of global resources (for example, IAM
              resources) with the resources that it records.

              Before you can set this option to true, you must set the allSupported option to true.

              If you set this option to true, when Config adds support for a new type of global resource,
              it starts recording resources of that type automatically.

              The configuration details for any global resource are the same in all regions. To prevent duplicate
              configuration items, you should consider customizing Config in only one region to record global
              resources.

            * resourceTypes (list[str], Optional):
              A comma-separated list that specifies the types of Amazon Web Services resources for which
              Config records configuration changes (for example, AWS::EC2::Instance or AWS::CloudTrail::Trail).

              To record all configuration changes, you must set the allSupported option to true.

              If you set this option to false, when Config adds support for a new type of resource, it will not record
              resources of that type unless you manually add that type to your recording group.

              For a list of valid resourceTypes values, see the resourceType Value column in Supported
              Amazon Web Services resource Types.

        recording (bool, Optional):
            Specifies recording status of the configuration recorder. Default is False.

        resource_id (str, Optional):
            The name of the recorder.

    Request syntax:
        .. code-block:: sls

            [aws-config-recorder]:
              aws.config.config_recorder.present:
              - name: 'string'
              - resource_id: 'string'
              - role_arn: 'string'
              - recording: 'string'
              - recording_group: 'dict'

    Returns:
         Dict[str, Any]

    Examples:
        .. code-block:: sls

             aws-config-recorder:
               aws.config.config_recorder.present:
                 - name: 'config_recorder'
                 - resource_id: 'config_recorder'
                 - role_arn: 'arn:aws:iam::012345678912:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig'
                 - recording: true
                 - recording_group:
                    allSupported: false
                    includeGlobalResourceTypes: false
                    resourceTypes:
                    - "AWS::ApiGateway::Stage"
    """
    result = dict(comment=(), old_state=None, new_state=None, name=name, result=True)
    before = None
    resource_updated: bool = False
    only_recording_status_changed: bool = False

    if resource_id:
        resource = await hub.exec.boto3.client.config.describe_configuration_recorders(
            ctx,
            ConfigurationRecorderNames=[name],
        )
        if not resource["result"]:
            result["comment"] = resource["comment"]
            result["result"] = False
            return result
        else:
            before = resource["ret"]["ConfigurationRecorders"][0]

    if before:
        before_recording_status = (
            await hub.exec.boto3.client.config.describe_configuration_recorder_status(
                ctx,
                ConfigurationRecorderNames=[name],
            )
        )
        if not before_recording_status["result"]:
            result["comment"] = before_recording_status["comment"]
            result["result"] = False
            return result
        before["recording"] = before_recording_status["ret"][
            "ConfigurationRecordersStatus"
        ][0]["recording"]

        resource_translated = await hub.tool.aws.config.conversion_utils.convert_raw_config_recorder_to_present(
            ctx,
            raw_resource=before,
            recording=before_recording_status["ret"]["ConfigurationRecordersStatus"][0][
                "recording"
            ],
        )
        result["old_state"] = resource_translated
        resource_updated = await hub.tool.aws.config.config_utils.is_resource_updated(
            before=resource_translated,
            role_arn=role_arn,
            recording_group=recording_group,
        )
        if not resource_updated and resource_translated["recording"] == recording:
            result["comment"] = (f"aws.config.config_recorder '{name}' already exists",)
            result["new_state"] = copy.deepcopy(result["old_state"])
            return result

        if not resource_updated and resource_translated["recording"] != recording:
            only_recording_status_changed = True

    if ctx.get("test", False):
        result["new_state"] = hub.tool.aws.test_state_utils.generate_test_state(
            enforced_state={},
            desired_state={
                "name": name,
                "role_arn": role_arn,
                "recording": recording,
                "resource_id": resource_id,
                "recording_group": recording_group,
            },
        )
        operation = (
            "update"
            if before
            and (resource_updated or resource_translated["recording"] != recording)
            else "create"
        )
        result["comment"] = (f"Would {operation} aws.config.config_recorder '{name}'",)
        return result

    if not only_recording_status_changed:
        update_ret = await hub.exec.boto3.client.config.put_configuration_recorder(
            ctx=ctx,
            ConfigurationRecorder={
                "name": name,
                "roleARN": role_arn,
                "recordingGroup": recording_group,
            },
        )
        if not update_ret["result"]:
            result["comment"] = update_ret["comment"]
            result["result"] = False
            return result

        after_ret = await hub.exec.boto3.client.config.describe_configuration_recorders(
            ctx,
            ConfigurationRecorderNames=[name],
        )
        if not after_ret["result"]:
            result["comment"] = after_ret["comment"]
            result["result"] = False
            return result

        result[
            "new_state"
        ] = await hub.tool.aws.config.conversion_utils.convert_raw_config_recorder_to_present(
            ctx,
            raw_resource=after_ret["ret"]["ConfigurationRecorders"][0],
            recording=None,
        )
        operation = "Created" if not before else "Updated"
        result["comment"] = (f"{operation} aws.config.config_recorder '{name}'",)
    else:
        result["new_state"] = copy.deepcopy(result["old_state"])

    # If it is create recorder case and recording is True
    #   OR
    # If it is update recorder case and recording status is changed
    if (not before and recording) or (before and recording != before["recording"]):
        if recording:
            recording_status_ret = (
                await hub.exec.boto3.client.config.start_configuration_recorder(
                    ctx=ctx, ConfigurationRecorderName=name
                )
            )
        else:
            recording_status_ret = (
                await hub.exec.boto3.client.config.stop_configuration_recorder(
                    ctx=ctx, ConfigurationRecorderName=name
                )
            )
        if not recording_status_ret["result"]:
            result["comment"] = recording_status_ret["comment"]
            result["result"] = False
            return result

        after_recording_status_ret = (
            await hub.exec.boto3.client.config.describe_configuration_recorder_status(
                ctx,
                ConfigurationRecorderNames=[name],
            )
        )
        if not after_recording_status_ret["result"]:
            result["comment"] = after_recording_status_ret["comment"]
            result["result"] = False
            return result
        result["new_state"]["recording"] = after_recording_status_ret["ret"][
            "ConfigurationRecordersStatus"
        ][0]["recording"]
        if only_recording_status_changed:
            result["comment"] = hub.tool.aws.comment_utils.update_comment(
                resource_type="aws.config.config_recorder", name=name
            )
    else:
        if not before:
            result["new_state"]["recording"] = False
        else:
            result["new_state"]["recording"] = before["recording"]
    return result


async def absent(hub, ctx, name: str, resource_id: str = None) -> Dict[str, Any]:
    """Deletes the configuration recorder.

    After the configuration recorder is deleted, Config will not record resource configuration changes until you
    create a new configuration recorder.

    Args:
        name(str):
            The name of the recorder.

        resource_id(str, Optional):
            AWS Config configuration recorder Name. Idem automatically considers this resource being absent
            if this field is not specified.

    Returns:
          Dict[str, Any]

    Examples:
          .. code-block:: sls

            aws-config-recorder:
              aws.config.config_recorder.absent:
                - name: 'config_recorder'
                - resource_id: 'config_recorder'
    """
    result = dict(comment=(), old_state=None, new_state=None, name=name, result=True)
    before = None
    if resource_id:
        resource = await hub.exec.boto3.client.config.describe_configuration_recorders(
            ctx,
            ConfigurationRecorderNames=[name],
        )
        if not resource["result"]:
            if "NoSuchConfigurationRecorderException" in str(resource["comment"]):
                result["comment"] = hub.tool.aws.comment_utils.already_absent_comment(
                    resource_type="aws.config.config_recorder", name=name
                )
                return result
            result["comment"] = resource["comment"]
            result["result"] = False
            return result
        else:
            before = resource["ret"]["ConfigurationRecorders"][0]

    if before:
        before_recording = (
            await hub.exec.boto3.client.config.describe_configuration_recorder_status(
                ctx,
                ConfigurationRecorderNames=[name],
            )
        )
        if not before_recording["result"]:
            result["comment"] = before_recording["comment"]
            result["result"] = False
            return result

        translated_resource = await hub.tool.aws.config.conversion_utils.convert_raw_config_recorder_to_present(
            ctx,
            raw_resource=before,
            recording=before_recording["ret"]["ConfigurationRecordersStatus"][0][
                "recording"
            ],
        )
        result["old_state"] = translated_resource
    else:
        result["comment"] = hub.tool.aws.comment_utils.already_absent_comment(
            resource_type="aws.config.config_recorder", name=name
        )
        return result

    if ctx.get("test", False):
        result["comment"] = hub.tool.aws.comment_utils.would_delete_comment(
            resource_type="aws.config.config_recorder", name=name
        )
        return result
    else:
        delete_ret = await hub.exec.boto3.client.config.delete_configuration_recorder(
            ctx, ConfigurationRecorderName=name
        )
        result["result"] = delete_ret["result"]
        if not result["result"]:
            result["comment"] = delete_ret["comment"]
            return result
        result["comment"] = hub.tool.aws.comment_utils.delete_comment(
            resource_type="aws.config.config_recorder", name=name
        )
    return result


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

    Return details about your config recorder.

    Returns:
        Dict[str, Any]

    Examples:
        .. code-block:: bash

            $ idem describe aws.config.config_recorder
    """
    result = {}
    ret = await hub.exec.boto3.client.config.describe_configuration_recorders(ctx)
    if not ret["result"]:
        hub.log.debug(f"Could not describe configuration recorder {ret['comment']}")
        return {}

    for resource in ret["ret"]["ConfigurationRecorders"]:
        name = resource["name"]
        recording_status = (
            await hub.exec.boto3.client.config.describe_configuration_recorder_status(
                ctx,
                ConfigurationRecorderNames=[name],
            )
        )
        if not recording_status["result"]:
            hub.log.warning(
                f"Could not fetch configuration recorder '{name}' status with error {recording_status['comment']}"
            )
            continue

        recorder_name = f"{name}-config-recorder"
        translated_resource = await hub.tool.aws.config.conversion_utils.convert_raw_config_recorder_to_present(
            ctx,
            raw_resource=resource,
            recording=recording_status["ret"]["ConfigurationRecordersStatus"][0][
                "recording"
            ],
        )
        result[recorder_name] = {
            "aws.config.config_recorder.present": [
                {parameter_key: parameter_value}
                for parameter_key, parameter_value in translated_resource.items()
            ]
        }
    return result
