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

from phidata.infra.aws.api_client import AwsApiClient
from phidata.infra.k8s.enums.api_version import ApiVersion
from phidata.infra.aws.resource.base import AwsResource
from phidata.infra.aws.resource.iam.role import IamRole
from phidata.infra.aws.resource.eks.cluster import EksCluster
from phidata.infra.k8s.resource.kubeconfig import (
    Kubeconfig,
    KubeconfigCluster,
    KubeconfigClusterConfig,
    KubeconfigContext,
    KubeconfigContextSpec,
    KubeconfigUser,
)
from phidata.utils.cli_console import print_info, print_error
from phidata.utils.log import logger


class EksKubeconfig(AwsResource):

    resource_type = "Kubeconfig"
    service_name = "na"

    # Optional: kubeconfig name, used for filtering during phi ws up/down
    name: Optional[str] = None
    # Required: EksCluster to generate the kubeconfig for
    eks_cluster: EksCluster
    # Required: Path to kubeconfig file
    kubeconfig_path: Path = Path.home().joinpath(".kube").joinpath("config").resolve()

    # Optional: cluster_name to use in kubeconfig, defaults to eks_cluster.name
    kubeconfig_cluster_name: Optional[str] = None
    # Optional: cluster_user to use in kubeconfig, defaults to eks_cluster.name
    kubeconfig_cluster_user: Optional[str] = None
    # Optional: cluster_context to use in kubeconfig, defaults to eks_cluster.name
    kubeconfig_cluster_context: Optional[str] = None

    # Optional: role to assume when signing the token
    kubeconfig_role: Optional[IamRole] = None
    # Optional: role arn to assume when signing the token
    kubeconfig_role_arn: Optional[str] = None

    # Dont delete this EksKubeconfig from the kubeconfig file
    skip_delete = True
    # Mark use_cache as False so the kubeconfig is re-created
    # every time phi ws up/down is run
    use_cache: bool = False

    def get_resource_name(self) -> Optional[str]:
        return self.name if self.name else f"{self.eks_cluster.name}-kubeconfig"

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

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            return self.write_kubeconfig(aws_client=aws_client)
        except Exception as e:
            print_error(f"{self.get_resource_type()} could not be created.")
            print_error(e)
        return False

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

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            kubeconfig_path = self.get_kubeconfig_path()
            if kubeconfig_path is not None:
                return Kubeconfig.read_from_file(kubeconfig_path)
        except Exception as e:
            print_error(f"Error reading {self.get_resource_type()}.")
            print_error(e)
        return self.active_resource

    def _update(self, aws_client: AwsApiClient) -> bool:
        """Updates the EksKubeconfig

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        print_info(f"Updating {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            return self.write_kubeconfig(aws_client=aws_client)
        except Exception as e:
            print_error(f"{self.get_resource_type()} could not be updated.")
            print_error(e)
        return False

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

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

        print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            return self.clean_kubeconfig(aws_client=aws_client)
        except Exception as e:
            print_error(f"{self.get_resource_type()} could not be deleted.")
            print_error(e)
        return False

    def get_kubeconfig_path(self) -> Optional[Path]:
        return self.kubeconfig_path or self.eks_cluster.kubeconfig_path

    def get_kubeconfig_cluster_name(self) -> str:
        return (
            self.kubeconfig_cluster_name
            or self.eks_cluster.get_kubeconfig_cluster_name()
        )

    def get_kubeconfig_user_name(self) -> str:
        return (
            self.kubeconfig_cluster_user or self.eks_cluster.get_kubeconfig_user_name()
        )

    def get_kubeconfig_context_name(self) -> str:
        return (
            self.kubeconfig_cluster_context
            or self.eks_cluster.get_kubeconfig_context_name()
        )

    def get_kubeconfig_role(self) -> Optional[IamRole]:
        return self.kubeconfig_role or self.eks_cluster.kubeconfig_role

    def get_kubeconfig_role_arn(self) -> Optional[str]:
        return self.kubeconfig_role_arn or self.eks_cluster.kubeconfig_role_arn

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

        # Step 1: Get the EksCluster to generate the kubeconfig for
        eks_cluster = self.eks_cluster._read(aws_client=aws_client)  # type: ignore
        if eks_cluster is None:
            logger.warning(f"EKSCluster not available: {self.eks_cluster.name}")
            return False

        # Step 2: Get EksCluster cert, endpoint & arn
        try:
            cluster_cert = (
                eks_cluster.get("cluster", {})
                .get("certificateAuthority", {})
                .get("data", None)
            )
            logger.debug(f"cluster_cert: {cluster_cert}")

            cluster_endpoint = eks_cluster.get("cluster", {}).get("endpoint", None)
            logger.debug(f"cluster_endpoint: {cluster_endpoint}")

            cluster_arn = eks_cluster.get("cluster", {}).get("arn", None)
            logger.debug(f"cluster_arn: {cluster_arn}")
        except Exception as e:
            print_error(f"Cannot read EKSCluster")
            print_error(e)
            return False

        # Step 3: Build Kubeconfig components
        # 3.1 Build KubeconfigCluster config
        cluster_name = self.get_kubeconfig_cluster_name()
        new_cluster = KubeconfigCluster(
            name=cluster_name,
            cluster=KubeconfigClusterConfig(
                server=str(cluster_endpoint),
                certificate_authority_data=str(cluster_cert),
            ),
        )

        # 3.2 Build KubeconfigUser config
        new_user_exec_args = ["eks", "get-token", "--cluster-name", cluster_name]
        if aws_client.aws_region is not None:
            new_user_exec_args.extend(["--region", aws_client.aws_region])
        # Assume the role if the role_arn is provided
        role = self.get_kubeconfig_role()
        role_arn = self.get_kubeconfig_role_arn()
        if role_arn is not None:
            new_user_exec_args.extend(["--role-arn", role_arn])
        # Otherwise if role is provided, use that to get the role arn
        elif role is not None:
            _arn = role.get_arn(aws_client=aws_client)
            if _arn is not None:
                new_user_exec_args.extend(["--role-arn", _arn])

        new_user_exec: Dict[str, Any] = {
            "apiVersion": ApiVersion.CLIENT_AUTHENTICATION_V1BETA1.value,
            "command": "aws",
            "args": new_user_exec_args,
        }
        if aws_client.aws_profile is not None:
            new_user_exec["env"] = [
                {"name": "AWS_PROFILE", "value": aws_client.aws_profile}
            ]

        new_user = KubeconfigUser(
            name=self.get_kubeconfig_user_name(),
            user={"exec": new_user_exec},
        )

        # 3.3 Build KubeconfigContext config
        new_context = KubeconfigContext(
            name=self.get_kubeconfig_context_name(),
            context=KubeconfigContextSpec(
                cluster=new_cluster.name,
                user=new_user.name,
            ),
        )
        current_context = new_context.name
        cluster_config: KubeconfigCluster

        # Step 4: Get existing Kubeconfig
        kubeconfig_path = self.get_kubeconfig_path()
        if kubeconfig_path is None:
            logger.error(f"kubeconfig_path is None")
            return False

        kubeconfig: Optional[Any] = Kubeconfig.read_from_file(kubeconfig_path)

        # Step 5: Parse through the existing config to determine if
        # an update is required. By the end of this logic
        # if write_kubeconfig = False then no changes to kubeconfig are needed
        # if write_kubeconfig = True then we should write the kubeconfig file
        write_kubeconfig = False

        # Kubeconfig exists and is valid
        if kubeconfig is not None and isinstance(kubeconfig, Kubeconfig):
            # Update Kubeconfig.clusters:
            # If a cluster with the same name exists in Kubeconfig.clusters
            #   - check if server and cert values match, if not, remove the existing cluster
            #   and add the new cluster config. Mark cluster_config_exists = True
            # If a cluster with the same name does not exist in Kubeconfig.clusters
            #   - add the new cluster config
            cluster_config_exists = False
            for idx, _cluster in enumerate(kubeconfig.clusters, start=0):
                if _cluster.name == new_cluster.name:
                    cluster_config_exists = True
                    if (
                        _cluster.cluster.server != new_cluster.cluster.server
                        or _cluster.cluster.certificate_authority_data
                        != new_cluster.cluster.certificate_authority_data
                    ):
                        logger.debug(
                            "Kubeconfig.cluster mismatch, updating cluster config"
                        )
                        removed_cluster_config = kubeconfig.clusters.pop(idx)
                        # logger.debug(
                        #     f"removed_cluster_config: {removed_cluster_config}"
                        # )
                        kubeconfig.clusters.append(new_cluster)
                        write_kubeconfig = True
            if not cluster_config_exists:
                logger.debug("Adding Kubeconfig.cluster")
                kubeconfig.clusters.append(new_cluster)
                write_kubeconfig = True

            # Update Kubeconfig.users:
            # If a user with the same name exists in Kubeconfig.users -
            #   check if user spec matches, if not, remove the existing user
            #   and add the new user config. Mark user_config_exists = True
            # If a user with the same name does not exist in Kubeconfig.users -
            #   add the new user config
            user_config_exists = False
            for idx, _user in enumerate(kubeconfig.users, start=0):
                if _user.name == new_user.name:
                    user_config_exists = True
                    if _user.user != new_user.user:
                        logger.debug("Kubeconfig.user mismatch, updating user config")
                        removed_user_config = kubeconfig.users.pop(idx)
                        # logger.debug(f"removed_user_config: {removed_user_config}")
                        kubeconfig.users.append(new_user)
                        write_kubeconfig = True
            if not user_config_exists:
                logger.debug("Adding Kubeconfig.user")
                kubeconfig.users.append(new_user)
                write_kubeconfig = True

            # Update Kubeconfig.contexts:
            # If a context with the same name exists in Kubeconfig.contexts -
            #   check if context spec matches, if not, remove the existing context
            #   and add the new context. Mark context_config_exists = True
            # If a context with the same name does not exist in Kubeconfig.contexts -
            #   add the new context config
            context_config_exists = False
            for idx, _context in enumerate(kubeconfig.contexts, start=0):
                if _context.name == new_context.name:
                    context_config_exists = True
                    if _context.context != new_context.context:
                        logger.debug(
                            "Kubeconfig.context mismatch, updating context config"
                        )
                        removed_context_config = kubeconfig.contexts.pop(idx)
                        # logger.debug(
                        #     f"removed_context_config: {removed_context_config}"
                        # )
                        kubeconfig.contexts.append(new_context)
                        write_kubeconfig = True
            if not context_config_exists:
                logger.debug("Adding Kubeconfig.context")
                kubeconfig.contexts.append(new_context)
                write_kubeconfig = True

            if (
                kubeconfig.current_context is None
                or kubeconfig.current_context != current_context
            ):
                logger.debug("Updating Kubeconfig.current_context")
                kubeconfig.current_context = current_context
                write_kubeconfig = True
        else:
            # Kubeconfig does not exist or is not valid
            # Create a new Kubeconfig
            logger.info(f"Creating new Kubeconfig")
            kubeconfig = Kubeconfig(
                clusters=[new_cluster],
                users=[new_user],
                contexts=[new_context],
                current_context=current_context,
            )
            write_kubeconfig = True

        # if kubeconfig:
        #     logger.debug("Kubeconfig:\n{}".format(kubeconfig.json(exclude_none=True, by_alias=True, indent=4)))

        # Step 5: Write Kubeconfig if an update is made
        if write_kubeconfig:
            return kubeconfig.write_to_file(kubeconfig_path)
        else:
            logger.info("Kubeconfig up-to-date")
        return True

    def clean_kubeconfig(self, aws_client: AwsApiClient) -> bool:
        logger.debug(f"TO_DO: Cleaning kubeconfig at {str(self.kubeconfig_path)}")
        return True

    ###########################################################################
    # def write_kubeconfig(self, aws_client: AwsApiClient) -> bool:
    #
    #     # Step 1: Get the EksCluster
    #     eks_cluster = self.eks_cluster._read(aws_client=aws_client)
    #     if eks_cluster is None:
    #         logger.warning("No EKSCluster available for kubeconfig update")
    #         return False
    #
    #     # Step 2: Get EksCluster cert, endpoint & arn
    #     try:
    #         cluster_cert = (
    #             eks_cluster.get("cluster", {})
    #             .get("certificateAuthority", {})
    #             .get("data", None)
    #         )
    #         logger.debug(f"cluster_cert: {cluster_cert}")
    #         cluster_endpoint = eks_cluster.get("cluster", {}).get("endpoint", None)
    #         logger.debug(f"cluster_endpoint: {cluster_endpoint}")
    #         cluster_arn = eks_cluster.get("cluster", {}).get("arn", None)
    #         logger.debug(f"cluster_arn: {cluster_arn}")
    #     except Exception as e:
    #         print_error(f"Cannot read EKSCluster")
    #         print_error(e)
    #         return False
    #
    #     from phidata.infra.k8s.enums.api_version import ApiVersion
    #     from phidata.infra.k8s.enums.kind import Kind
    #     from phidata.infra.k8s.resource.kubeconfig import (
    #         Kubeconfig,
    #         KubeconfigCluster,
    #         KubeconfigClusterConfig,
    #         KubeconfigContext,
    #         KubeconfigContextSpec,
    #         KubeconfigUser,
    #     )
    #
    #     # Build cluster config
    #     new_cluster = KubeconfigCluster(
    #         name=self.get_kubeconfig_cluster_name(),
    #         cluster=KubeconfigClusterConfig(
    #             server=str(cluster_endpoint),
    #             certificate_authority_data=str(cluster_cert),
    #         ),
    #     )
    #
    #     # Build user config
    #     new_user_exec_args = ["eks", "get-token", "--cluster-name", self.name]
    #     if aws_client.aws_region is not None:
    #         new_user_exec_args.extend(["--region", aws_client.aws_region])
    #     # Assume the role using the role_arn if provided
    #     if self.role_arn is not None:
    #         new_user_exec_args.extend(["--role-arn", self.role_arn])
    #     # Otherwise if role is provided, use that to get the role arn
    #     elif self.role is not None:
    #         _arn = self.role.get_arn(aws_client=aws_client)
    #         if _arn is not None:
    #             new_user_exec_args.extend(["--role-arn", _arn])
    #
    #     new_user_exec: Dict[str, Any] = {
    #         "apiVersion": ApiVersion.CLIENT_AUTHENTICATION_V1BETA1.value,
    #         "command": "aws",
    #         "args": new_user_exec_args,
    #     }
    #     if aws_client.aws_profile is not None:
    #         new_user_exec["env"] = [
    #             {"name": "AWS_PROFILE", "value": aws_client.aws_profile}
    #         ]
    #
    #     new_user = KubeconfigUser(
    #         name=self.get_kubeconfig_user_name(),
    #         user={"exec": new_user_exec},
    #     )
    #
    #     # Build context config
    #     new_context = KubeconfigContext(
    #         name=self.get_kubeconfig_context_name(),
    #         context=KubeconfigContextSpec(
    #             cluster=new_cluster.name,
    #             user=new_user.name,
    #         ),
    #     )
    #     current_context = new_context.name
    #     cluster_config: KubeconfigCluster
    #
    #     # Step 3: Read existing Kubeconfig
    #     kubeconfig = self._read(aws_client=aws_client)
    #
    #     # Step 4: Parse through the existing config to determine if
    #     # an update is required. By the end of this logic if write_kubeconfig = False
    #     # then no changes to kubeconfig are needed
    #     write_kubeconfig = False
    #
    #     if kubeconfig is None or not isinstance(kubeconfig, Kubeconfig):
    #         logger.info(f"Creating new Kubeconfig")
    #         kubeconfig = Kubeconfig(
    #             api_version=ApiVersion.CORE_V1,
    #             kind=Kind.CONFIG,
    #             clusters=[new_cluster],
    #             users=[new_user],
    #             contexts=[new_context],
    #             current_context=current_context,
    #         )
    #         write_kubeconfig = True
    #     else:
    #         # Update Kubeconfig.clusters:
    #         # If a cluster with the same name exists in Kubeconfig.clusters
    #         #   - check if server and cert values match, if not, remove the existing cluster
    #         #   and add the new cluster config. Mark cluster_config_exists = True
    #         # If a cluster with the same name does not exist in Kubeconfig.clusters
    #         #   - add the new cluster config
    #         cluster_config_exists = False
    #         for idx, _cluster in enumerate(kubeconfig.clusters, start=0):
    #             if _cluster.name == new_cluster.name:
    #                 cluster_config_exists = True
    #                 if (
    #                     _cluster.cluster.server != new_cluster.cluster.server
    #                     or _cluster.cluster.certificate_authority_data
    #                     != new_cluster.cluster.certificate_authority_data
    #                 ):
    #                     logger.debug(
    #                         "Kubeconfig.cluster mismatch, updating cluster config"
    #                     )
    #                     removed_cluster_config = kubeconfig.clusters.pop(idx)
    #                     # logger.debug(
    #                     #     f"removed_cluster_config: {removed_cluster_config}"
    #                     # )
    #                     kubeconfig.clusters.append(new_cluster)
    #                     write_kubeconfig = True
    #         if not cluster_config_exists:
    #             logger.debug("Adding Kubeconfig.cluster")
    #             kubeconfig.clusters.append(new_cluster)
    #             write_kubeconfig = True
    #
    #         # Update Kubeconfig.users:
    #         # If a user with the same name exists in Kubeconfig.users -
    #         #   check if user spec matches, if not, remove the existing user
    #         #   and add the new user config. Mark user_config_exists = True
    #         # If a user with the same name does not exist in Kubeconfig.users -
    #         #   add the new user config
    #         user_config_exists = False
    #         for idx, _user in enumerate(kubeconfig.users, start=0):
    #             if _user.name == new_user.name:
    #                 user_config_exists = True
    #                 if _user.user != new_user.user:
    #                     logger.debug("Kubeconfig.user mismatch, updating user config")
    #                     removed_user_config = kubeconfig.users.pop(idx)
    #                     # logger.debug(f"removed_user_config: {removed_user_config}")
    #                     kubeconfig.users.append(new_user)
    #                     write_kubeconfig = True
    #         if not user_config_exists:
    #             logger.debug("Adding Kubeconfig.user")
    #             kubeconfig.users.append(new_user)
    #             write_kubeconfig = True
    #
    #         # Update Kubeconfig.contexts:
    #         # If a context with the same name exists in Kubeconfig.contexts -
    #         #   check if context spec matches, if not, remove the existing context
    #         #   and add the new context. Mark context_config_exists = True
    #         # If a context with the same name does not exist in Kubeconfig.contexts -
    #         #   add the new context config
    #         context_config_exists = False
    #         for idx, _context in enumerate(kubeconfig.contexts, start=0):
    #             if _context.name == new_context.name:
    #                 context_config_exists = True
    #                 if _context.context != new_context.context:
    #                     logger.debug(
    #                         "Kubeconfig.context mismatch, updating context config"
    #                     )
    #                     removed_context_config = kubeconfig.contexts.pop(idx)
    #                     # logger.debug(
    #                     #     f"removed_context_config: {removed_context_config}"
    #                     # )
    #                     kubeconfig.contexts.append(new_context)
    #                     write_kubeconfig = True
    #         if not context_config_exists:
    #             logger.debug("Adding Kubeconfig.context")
    #             kubeconfig.contexts.append(new_context)
    #             write_kubeconfig = True
    #
    #         if (
    #             kubeconfig.current_context is None
    #             or kubeconfig.current_context != current_context
    #         ):
    #             logger.debug("Updating Kubeconfig.current_context")
    #             kubeconfig.current_context = current_context
    #             write_kubeconfig = True
    #
    #     # if kubeconfig:
    #     #     logger.debug("Kubeconfig:\n{}".format(kubeconfig.json(exclude_none=True, by_alias=True, indent=4)))
    #
    #     # Step 5: Write Kubeconfig if an update is made
    #     if write_kubeconfig:
    #         return self._write(kubeconfig)
    #     else:
    #         logger.info("Kubeconfig up-to-date")
    #     return True
    #
    # def clean_kubeconfig(self, aws_client: AwsApiClient) -> bool:
    #     logger.debug(f"TO_DO: Cleaning kubeconfig at {str(self.kubeconfig_path)}")
    #     return True
