from pathlib import Path
from typing import Optional, Any, List, Dict
from typing_extensions import Literal

from pydantic import BaseModel

from phidata.infra.aws.api_client import AwsApiClient
from phidata.infra.aws.resource.base import AwsResource
from phidata.utils.cli_console import print_info, print_error, print_subheading
from phidata.utils.log import logger


class CertificateSummary(BaseModel):
    CertificateArn: str
    DomainName: Optional[str] = None


class AcmCertificate(AwsResource):
    """
    You can use Amazon Web Services Certificate Manager (ACM) to manage SSL/TLS
    certificates for your Amazon Web Services-based websites and applications.

    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/acm.html
    """

    resource_type = "AcmCertificate"
    service_name = "acm"

    # website base domain name, such as example.com
    name: str
    # Fully qualified domain name (FQDN), such as www.example.com,
    # that you want to secure with an ACM certificate.
    #
    # Use an asterisk (*) to create a wildcard certificate that protects several sites in the same domain.
    # For example, *.example.com protects www.example.com, site.example.com, and images.example.com.

    # The first domain name you enter cannot exceed 64 octets, including periods.
    # Each subsequent Subject Alternative Name (SAN), however, can be up to 253 octets in length.

    # If None, defaults to "*.name"
    domain_name: Optional[str] = None
    # The method you want to use if you are requesting a public certificate to validate that you own or control domain.
    # You can validate with DNS or validate with email .
    # We recommend that you use DNS validation.
    validation_method: Literal["EMAIL", "DNS"] = "DNS"
    # Additional FQDNs to be included in the Subject Alternative Name extension of the ACM certificate.
    # For example, add the name www.example.net to a certificate for which the DomainName field is www.example.com
    # if users can reach your site by using either name. The maximum number of domain names that you can add to an
    # ACM certificate is 100. However, the initial quota is 10 domain names. If you need more than 10 names,
    # you must request a quota increase.
    subject_alternative_names: Optional[List[str]] = None
    # Customer chosen string that can be used to distinguish between calls to RequestCertificate .
    # Idempotency tokens time out after one hour. Therefore, if you call RequestCertificate multiple times with
    # the same idempotency token within one hour, ACM recognizes that you are requesting only one certificate
    # and will issue only one. If you change the idempotency token for each call, ACM recognizes that you are
    # requesting multiple certificates.
    idempotency_token: Optional[str] = None
    # The domain name that you want ACM to use to send you emails so that you can validate domain ownership.
    domain_validation_options: Optional[List[dict]] = None
    options: Optional[dict] = None
    certificate_authority_arn: Optional[str] = None
    tags: Optional[List[dict]] = None

    # If True, stores the certificate summary in the file `certificate_summary_file`
    store_cert_summary: bool = False
    # Path for the certificate_summary_file
    certificate_summary_file: Optional[Path] = None

    wait_for_creation = False

    def _create(self, aws_client: AwsApiClient) -> bool:
        """Requests an ACM certificate for use with other Amazon Web Services.

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

        # Step 1: Build ACM configuration
        domain_name = self.domain_name
        if domain_name is None:
            domain_name = self.name
        print_info(f"Requesting AcmCertificate for: {domain_name}")

        # create a dict of args which are not null, otherwise aws type validation fails
        not_null_args: Dict[str, Any] = {}
        if self.subject_alternative_names is not None:
            not_null_args["SubjectAlternativeNames"] = self.subject_alternative_names
            print_info("SANs:")
            for san in self.subject_alternative_names:
                print_info(f"    - {san}")
        if self.idempotency_token is not None:
            not_null_args["IdempotencyToken"] = self.idempotency_token
        if self.domain_validation_options is not None:
            not_null_args["DomainValidationOptions"] = self.domain_validation_options
        if self.options is not None:
            not_null_args["Options"] = self.options
        if self.certificate_authority_arn is not None:
            not_null_args["CertificateAuthorityArn"] = self.certificate_authority_arn
        if self.tags is not None:
            not_null_args["Tags"] = self.tags

        # Step 2: Request AcmCertificate
        service_client = self.get_service_client(aws_client)
        try:
            request_cert_response = service_client.request_certificate(
                DomainName=domain_name,
                ValidationMethod=self.validation_method,
                **not_null_args,
            )
            logger.debug(f"AcmCertificate: {request_cert_response}")

            # Validate AcmCertificate creation
            certificate_arn = request_cert_response.get("CertificateArn", None)
            if certificate_arn is not None:
                print_subheading(f"---- Please Note: Certificate ARN ----")
                print_info(f"{certificate_arn}")
                print_subheading(f"--------\n")
                self.active_resource = request_cert_response
                return True
        except Exception as e:
            print_error(f"{self.get_resource_type()} could not be created.")
            print_error(e)
        return False

    def post_create(self, aws_client: AwsApiClient) -> bool:

        # Wait for AcmCertificate to be validated
        if self.wait_for_creation:
            try:
                print_info(f"Waiting for {self.get_resource_type()} to be created.")
                waiter = self.get_service_client(aws_client).get_waiter(
                    "certificate_validated"
                )
                certificate_arn = self.get_certificate_arn(aws_client)
                waiter.wait(
                    CertificateArn=certificate_arn,
                    WaiterConfig={
                        "Delay": self.waiter_delay,
                        "MaxAttempts": self.waiter_max_attempts,
                    },
                )
            except Exception as e:
                print_error("Waiter failed.")
                print_error(e)

        # Store cert summary if needed
        if self.store_cert_summary:
            if self.certificate_summary_file is None:
                print_error("certificate_summary_file not provided")
                return False

            try:
                read_cert_summary = self._read(aws_client)
                if read_cert_summary is None:
                    print_error("certificate_summary not available")
                    return False

                cert_summary = CertificateSummary(**read_cert_summary)
                if not self.certificate_summary_file.exists():
                    self.certificate_summary_file.parent.mkdir(
                        parents=True, exist_ok=True
                    )
                    self.certificate_summary_file.touch(exist_ok=True)
                self.certificate_summary_file.write_text(cert_summary.json(indent=2))
                print_info(
                    f"Certificate Summary stored at: {str(self.certificate_summary_file)}"
                )
            except Exception as e:
                print_error("Could not writing Certificate Summary to file")
                print_error(e)

        return True

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

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        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:
            list_certificate_response = service_client.list_certificates()
            # logger.debug(f"AcmCertificate: {list_certificate_response}")

            current_cert = None
            certificate_summary_list = list_certificate_response.get(
                "CertificateSummaryList", []
            )
            for cert_summary in certificate_summary_list:
                domain = cert_summary.get("DomainName", None)
                if domain is not None and domain == self.name:
                    current_cert = cert_summary
            # logger.debug(f"current_cert: {current_cert}")
            # logger.debug(f"current_cert type: {type(current_cert)}")

            if current_cert is not None:
                logger.debug(f"AcmCertificate found: {self.name}")
                self.active_resource = current_cert
        except ClientError as ce:
            logger.debug(f"ClientError: {ce}")
        except Exception as e:
            print_error(f"Error reading {self.get_resource_type()}.")
            print_error(e)
        return self.active_resource

    def _delete(self, aws_client: AwsApiClient) -> bool:
        """Deletes a certificate and its associated private key.

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

        service_client = self.get_service_client(aws_client)
        self.active_resource = None
        try:
            certificate_arn = self.get_certificate_arn(aws_client)
            if certificate_arn is not None:
                delete_cert_response = service_client.delete_certificate(
                    CertificateArn=certificate_arn,
                )
                logger.debug(f"delete_cert_response: {delete_cert_response}")
                print_info(f"AcmCertificate deleted: {self.name}")
            else:
                print_info("AcmCertificate not found")
            return True
        except Exception as e:
            print_error(e)
        return False

    def get_certificate_arn(self, aws_client: AwsApiClient) -> Optional[str]:
        cert_summary = self._read(aws_client)
        if cert_summary is None:
            return None
        cert_arn = cert_summary.get("CertificateArn", None)
        return cert_arn
