from typing import Optional, Any, Dict, List, Union

from phi.aws.api_client import AwsApiClient
from phi.aws.resource.base import AwsResource
from phi.aws.resource.reference import AwsReference
from phi.aws.resource.cloudformation.stack import CloudFormationStack
from phi.cli.console import print_info
from phi.utils.log import logger


class CacheSubnetGroup(AwsResource):
    """
    Reference:
    - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elasticache.html#ElastiCache.Client.create_cache_subnet_group

    Creates a cache subnet group.
    """

    resource_type: Optional[str] = "CacheSubnetGroup"
    service_name: str = "elasticache"

    # A name for the cache subnet group. This value is stored as a lowercase string.
    # Constraints: Must contain no more than 255 alphanumeric characters or hyphens.
    name: str
    # A description for the cache subnet group.
    description: Optional[str] = None
    # A list of VPC subnet IDs for the cache subnet group.
    subnet_ids: Optional[Union[List[str], AwsReference]] = None
    # Get Subnet IDs from a VPC CloudFormationStack
    # First gets private subnets from the vpc stack, then public subnets
    vpc_stack: Optional[CloudFormationStack] = None
    # A list of tags to be added to this resource.
    tags: Optional[List[Dict[str, str]]] = None

    def get_subnet_ids(self, aws_client: AwsApiClient) -> List[str]:
        """Returns the subnet_ids for the CacheSubnetGroup

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        subnet_ids = []
        if self.subnet_ids is not None:
            if isinstance(self.subnet_ids, list):
                logger.debug("Getting subnet_ids from list")
                subnet_ids = self.subnet_ids
            elif isinstance(self.subnet_ids, AwsReference):
                logger.debug("Getting subnet_ids from reference")
                subnet_ids = self.subnet_ids.get_reference(aws_client=aws_client)
        if len(subnet_ids) == 0 and self.vpc_stack is not None:
            logger.debug("Getting private subnet_ids from vpc stack")
            private_subnet_ids = self.vpc_stack.get_private_subnets(aws_client=aws_client)
            if private_subnet_ids is not None:
                subnet_ids.extend(private_subnet_ids)
            if len(subnet_ids) == 0:
                logger.debug("Getting public subnet_ids from vpc stack")
                public_subnet_ids = self.vpc_stack.get_public_subnets(aws_client=aws_client)
                if public_subnet_ids is not None:
                    subnet_ids.extend(public_subnet_ids)
        return subnet_ids

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

        Args:
            aws_client: The AwsApiClient for the current cluster
        """

        print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            # Get subnet_ids
            subnet_ids = self.get_subnet_ids(aws_client=aws_client)

            # create a dict of args which are not null, otherwise aws type validation fails
            not_null_args: Dict[str, Any] = {}
            if self.tags:
                not_null_args["Tags"] = self.tags

            # Create CacheSubnetGroup
            service_client = self.get_service_client(aws_client)
            create_response = service_client.create_cache_subnet_group(
                CacheSubnetGroupName=self.name,
                CacheSubnetGroupDescription=self.description or f"Created for {self.name}",
                SubnetIds=subnet_ids,
                **not_null_args,
            )
            logger.debug(f"create_response type: {type(create_response)}")
            logger.debug(f"create_response: {create_response}")

            self.active_resource = create_response.get("CacheSubnetGroup", None)
            if self.active_resource is not None:
                print_info(f"{self.get_resource_type()}: {self.get_resource_name()} created")
                logger.debug(f"CacheSubnetGroup: {self.active_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 CacheSubnetGroup

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

        logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            service_client = self.get_service_client(aws_client)
            describe_response = service_client.describe_cache_subnet_groups(CacheSubnetGroupName=self.name)
            logger.debug(f"describe_response type: {type(describe_response)}")
            logger.debug(f"describe_response: {describe_response}")

            cache_subnet_group_list = describe_response.get("CacheSubnetGroups", None)
            if cache_subnet_group_list is not None and isinstance(cache_subnet_group_list, list):
                for _cache_subnet_group in cache_subnet_group_list:
                    _cache_sg_name = _cache_subnet_group.get("CacheSubnetGroupName", None)
                    if _cache_sg_name == self.name:
                        self.active_resource = _cache_subnet_group
                        break

            if self.active_resource is None:
                logger.debug(f"No {self.get_resource_type()} found")
                return None

            logger.debug(f"CacheSubnetGroup: {self.active_resource}")
        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 CacheSubnetGroup

        Args:
            aws_client: The AwsApiClient for the current cluster
        """

        print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            service_client = self.get_service_client(aws_client)
            self.active_resource = None

            delete_response = service_client.delete_cache_subnet_group(CacheSubnetGroupName=self.name)
            logger.debug(f"delete_response: {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:
        """Updates the CacheSubnetGroup

        Args:
            aws_client: The AwsApiClient for the current cluster
        """

        print_info(f"Updating {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            # Get subnet_ids
            subnet_ids = self.get_subnet_ids(aws_client=aws_client)

            # Update CacheSubnetGroup
            service_client = self.get_service_client(aws_client)
            update_response = service_client.modify_cache_subnet_group(
                CacheSubnetGroupName=self.name,
                CacheSubnetGroupDescription=self.description or f"Created for {self.name}",
                SubnetIds=subnet_ids,
            )
            logger.debug(f"update_response: {update_response}")

            self.active_resource = update_response.get("CacheSubnetGroup", None)
            if self.active_resource is not None:
                print_info(f"{self.get_resource_type()}: {self.get_resource_name()} updated")
                return True
        except Exception as e:
            logger.error(f"{self.get_resource_type()} could not be updated.")
            logger.error(e)
        return False
