import json
from pathlib import Path
from typing import Optional, Any, Dict, List

from phi.aws.api_client import AwsApiClient
from phi.aws.resource.base import AwsResource
from phi.cli.console import print_info
from phi.utils.log import logger


class SecretsManager(AwsResource):
    """
    Reference:
    - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html
    """

    resource_type: Optional[str] = "Secret"
    service_name: str = "secretsmanager"

    # The name of the secret.
    name: str
    client_request_token: Optional[str] = None
    # The description of the secret.
    description: Optional[str] = None
    kms_key_id: Optional[str] = None
    # The binary data to encrypt and store in the new version of the secret.
    # We recommend that you store your binary data in a file and then pass the contents of the file as a parameter.
    secret_binary: Optional[bytes] = None
    # The text data to encrypt and store in this new version of the secret.
    # We recommend you use a JSON structure of key/value pairs for your secret value.
    # Either SecretString or SecretBinary must have a value, but not both.
    secret_string: Optional[str] = None
    # A list of tags to attach to the secret.
    tags: Optional[List[Dict[str, str]]] = None
    # A list of Regions and KMS keys to replicate secrets.
    add_replica_regions: Optional[List[Dict[str, str]]] = None
    # Specifies whether to overwrite a secret with the same name in the destination Region.
    force_overwrite_replica_secret: Optional[str] = None

    # Read secret key/value pairs from yaml files
    secret_files: Optional[List[Path]] = None
    # Read secret key/value pairs from yaml files in a directory
    secrets_dir: Optional[Path] = None
    # Force delete the secret without recovery
    force_delete: Optional[bool] = True

    # Provided by api on create
    secret_arn: Optional[str] = None
    secret_name: Optional[str] = None
    secret_value: Optional[dict] = None

    cached_secret: Optional[Dict[str, Any]] = None

    def read_secrets_from_files(self) -> Dict[str, Any]:
        """Reads secrets from files"""
        from phi.utils.yaml_io import read_yaml_file

        secret_dict: Dict[str, Any] = {}
        if self.secret_files:
            for f in self.secret_files:
                _s = read_yaml_file(f)
                if _s is not None:
                    secret_dict.update(_s)
        if self.secrets_dir:
            for f in self.secrets_dir.glob("*.yaml"):
                _s = read_yaml_file(f)
                if _s is not None:
                    secret_dict.update(_s)
            for f in self.secrets_dir.glob("*.yml"):
                _s = read_yaml_file(f)
                if _s is not None:
                    secret_dict.update(_s)
        return secret_dict

    def _create(self, aws_client: AwsApiClient) -> bool:
        """Creates the SecretsManager

        Args:
            aws_client: The AwsApiClient for the current secret
        """
        print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}")

        # Step 1: Read secrets from files
        secret_dict: Dict[str, Any] = self.read_secrets_from_files()

        # Step 2: Add secret_string if provided
        if self.secret_string is not None:
            secret_dict.update(json.loads(self.secret_string))

        # Step 3: Build secret_string
        secret_string: Optional[str] = json.dumps(secret_dict) if len(secret_dict) > 0 else None

        # Step 4: Build SecretsManager configuration
        # create a dict of args which are not null, otherwise aws type validation fails
        not_null_args: Dict[str, Any] = {}
        if self.client_request_token:
            not_null_args["ClientRequestToken"] = self.client_request_token
        if self.description:
            not_null_args["Description"] = self.description
        if self.kms_key_id:
            not_null_args["KmsKeyId"] = self.kms_key_id
        if self.secret_binary:
            not_null_args["SecretBinary"] = self.secret_binary
        if secret_string:
            not_null_args["SecretString"] = secret_string
        if self.tags:
            not_null_args["Tags"] = self.tags
        if self.add_replica_regions:
            not_null_args["AddReplicaRegions"] = self.add_replica_regions
        if self.force_overwrite_replica_secret:
            not_null_args["ForceOverwriteReplicaSecret"] = self.force_overwrite_replica_secret

        # Step 3: Create SecretsManager
        service_client = self.get_service_client(aws_client)
        try:
            created_resource = service_client.create_secret(
                Name=self.name,
                **not_null_args,
            )
            logger.debug(f"SecretsManager: {created_resource}")

            # Validate SecretsManager creation
            self.secret_arn = created_resource.get("ARN", None)
            self.secret_name = created_resource.get("Name", None)
            logger.debug(f"secret_arn: {self.secret_arn}")
            logger.debug(f"secret_name: {self.secret_name}")
            if self.secret_arn is not None:
                self.cached_secret = secret_dict
                self.active_resource = created_resource
                return True
        except Exception as e:
            logger.error(f"{self.get_resource_type()} could not be created.")
            logger.error(e)
        return False

    def _read(self, aws_client: AwsApiClient) -> Optional[Any]:
        """Returns the SecretsManager

        Args:
            aws_client: The AwsApiClient for the current secret
        """
        logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}")

        from botocore.exceptions import ClientError

        service_client = self.get_service_client(aws_client)
        try:
            describe_response = service_client.describe_secret(SecretId=self.name)
            logger.debug(f"SecretsManager: {describe_response}")

            self.secret_arn = describe_response.get("ARN", None)
            self.secret_name = describe_response.get("Name", None)
            describe_response.get("DeletedDate", None)
            logger.debug(f"secret_arn: {self.secret_arn}")
            logger.debug(f"secret_name: {self.secret_name}")
            # logger.debug(f"secret_deleted_date: {secret_deleted_date}")
            if self.secret_arn is not None:
                # print_info(f"SecretsManager available: {self.name}")
                self.active_resource = describe_response
        except ClientError as ce:
            logger.debug(f"ClientError: {ce}")
        except Exception as e:
            logger.error(f"Error reading {self.get_resource_type()}.")
            logger.error(e)
        return self.active_resource

    def _delete(self, aws_client: AwsApiClient) -> bool:
        """Deletes the SecretsManager

        Args:
            aws_client: The AwsApiClient for the current secret
        """
        print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}")

        service_client = self.get_service_client(aws_client)
        self.active_resource = None
        self.secret_value = None
        try:
            delete_response = service_client.delete_secret(
                SecretId=self.name, ForceDeleteWithoutRecovery=self.force_delete
            )
            logger.debug(f"SecretsManager: {delete_response}")
            return True
        except Exception as e:
            logger.error(f"{self.get_resource_type()} could not be deleted.")
            logger.error("Please try again or delete resources manually.")
            logger.error(e)
        return False

    def _update(self, aws_client: AwsApiClient) -> bool:
        """Update SecretsManager"""
        print_info(f"Updating {self.get_resource_type()}: {self.get_resource_name()}")

        # Initialize final secret_dict
        secret_dict: Dict[str, Any] = {}

        # Step 1: Read secrets from AWS SecretsManager
        existing_secret_dict = self.get_secrets_as_dict()
        # logger.debug(f"existing_secret_dict: {existing_secret_dict}")
        if existing_secret_dict is not None:
            secret_dict.update(existing_secret_dict)

        # Step 2: Read secrets from files
        new_secret_dict: Dict[str, Any] = self.read_secrets_from_files()
        if len(new_secret_dict) > 0:
            secret_dict.update(new_secret_dict)

        # Step 3: Add secret_string is provided
        if self.secret_string is not None:
            secret_dict.update(json.loads(self.secret_string))

        # Step 3: Update AWS SecretsManager
        service_client = self.get_service_client(aws_client)
        self.active_resource = None
        self.secret_value = None
        try:
            create_response = service_client.update_secret(
                SecretId=self.name,
                SecretString=json.dumps(secret_dict),
            )
            logger.debug(f"SecretsManager: {create_response}")
            return True
        except Exception as e:
            logger.error(f"{self.get_resource_type()} could not be Updated.")
            logger.error(e)
        return False

    def get_secrets_as_dict(self, aws_client: Optional[AwsApiClient] = None) -> Optional[Dict[str, Any]]:
        """Get secret value

        Args:
            aws_client: The AwsApiClient for the current secret
        """
        from botocore.exceptions import ClientError

        if self.cached_secret is not None:
            return self.cached_secret

        logger.debug(f"Getting {self.get_resource_type()}: {self.get_resource_name()}")
        client: AwsApiClient = aws_client or self.get_aws_client()
        service_client = self.get_service_client(client)
        try:
            secret_value = service_client.get_secret_value(SecretId=self.name)
            # logger.debug(f"SecretsManager: {secret_value}")

            if secret_value is None:
                logger.warning(f"Secret Empty: {self.name}")
                return None

            self.secret_value = secret_value
            self.secret_arn = secret_value.get("ARN", None)
            self.secret_name = secret_value.get("Name", None)

            secret_string = secret_value.get("SecretString", None)
            if secret_string is not None:
                self.cached_secret = json.loads(secret_string)
                return self.cached_secret

            secret_binary = secret_value.get("SecretBinary", None)
            if secret_binary is not None:
                self.cached_secret = json.loads(secret_binary.decode("utf-8"))
                return self.cached_secret
        except ClientError as ce:
            logger.debug(f"ClientError: {ce}")
        except Exception as e:
            logger.error(f"Error reading {self.get_resource_type()}.")
            logger.error(e)
        return None

    def get_secret_value(self, secret_name: str, aws_client: Optional[AwsApiClient] = None) -> Optional[Any]:
        secret_dict = self.get_secrets_as_dict(aws_client=aws_client)
        if secret_dict is not None:
            return secret_dict.get(secret_name, None)
        return None
