# -*- coding: utf-8 -*-

"""
Setup automation for AWS services.
"""

import typing as T
import os
import subprocess
import dataclasses
from functools import cached_property

try:
    import boto3
    import botocore.exceptions
except ImportError:  # pragma: no cover
    pass

from .vendor.emoji import Emoji
from .vendor.better_pathlib import temp_cwd

from .logger import logger
from .runtime import IS_CI
from .helpers import sha256_of_bytes, print_command

if T.TYPE_CHECKING:  # pragma: no cover
    from .define import PyWf
    from boto3 import Session
    from mypy_boto3_codeartifact.client import CodeArtifactClient


@dataclasses.dataclass
class PyWfAws:  # pragma: no cover
    """
    Namespace class for AWS setup automation.
    """

    @cached_property
    def boto_ses_codeartifact(self: "PyWf") -> "Session":
        if self.aws_codeartifact_profile:
            profile_name = self.aws_codeartifact_profile
        else:
            profile_name = None
        return boto3.Session(
            profile_name=profile_name,
            region_name=self.aws_region,
        )

    @cached_property
    def codeartifact_client(self: "PyWf") -> "CodeArtifactClient":
        return self.boto_ses_codeartifact.client("codeartifact")

    def get_codeartifact_repository_endpoint(self: "PyWf") -> str:
        """
        Reference:

        - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codeartifact/client/get_repository_endpoint.html
        """
        res = self.codeartifact_client.get_repository_endpoint(
            domain=self.aws_codeartifact_domain,
            repository=self.aws_codeartifact_repository,
            format="pypi",
        )
        return res["repositoryEndpoint"]

    def get_codeartifact_authorization_token(self: "PyWf") -> str:
        """
        Reference:

        - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codeartifact/client/get_authorization_token.html
        """
        res = self.codeartifact_client.get_authorization_token(
            domain=self.aws_codeartifact_domain,
        )
        return res["authorizationToken"]

    @property
    def poetry_secondary_source_name(self: "PyWf") -> str:
        return self.aws_codeartifact_repository

    @logger.emoji_block(
        msg="Add CodeArtifact as a secondary source",
        emoji=Emoji.install,
    )
    def _poetry_source_add_codeartifact(
        self: "PyWf",
        codeartifact_repository_endpoint: str,
        real_run: bool = True,
        quiet: bool = False,
    ):
        """
        Run:

        .. code-block:: bash

            poetry source add --secondary ${source_name} "https://${domain_name}-${aws_account_id}.d.codeartifact.${aws_region}.amazonaws.com/pypi/${repository_name}/simple/"
        """
        args = [
            f"{self.path_bin_poetry}",
            "source",
            "add",
            "--priority=supplemental",
            self.poetry_secondary_source_name,
            f"{codeartifact_repository_endpoint}simple/",
        ]
        print_command(args)
        if real_run:
            with temp_cwd(self.dir_project_root):
                subprocess.run(args, check=True)

    def poetry_source_add_codeartifact(
        self: "PyWf",
        codeartifact_repository_endpoint: str,
        real_run: bool = True,
        verbose: bool = True,
    ):
        with logger.disabled(disable=not verbose):
            self._poetry_source_add_codeartifact(
                codeartifact_repository_endpoint=codeartifact_repository_endpoint,
                real_run=real_run,
                quiet=not verbose,
            )

    poetry_source_add_codeartifact.__doc__ = _poetry_source_add_codeartifact.__doc__

    def poetry_authorization(
        self: "PyWf",
        codeartifact_authorization_token: str,
        real_run: bool = True,
        verbose: bool = True,
    ):
        """
        Set environment variables to allow Poetry to authenticate with CodeArtifact.
        """
        token = codeartifact_authorization_token
        source_name = self.poetry_secondary_source_name.upper()
        if real_run:
            os.environ[f"POETRY_HTTP_BASIC_{source_name}_USERNAME"] = "aws"
            os.environ[f"POETRY_HTTP_BASIC_{source_name}_PASSWORD"] = token

    def twine_authorization(
        self: "PyWf",
        real_run: bool = True,
        verbose: bool = True,
    ):
        """
        Run

        .. code-block:: bash

            aws codeartifact login --tool twine \
                --domain ${domain_name} \
                --domain-owner ${aws_account_id} \
                --repository ${repo_name} \
                --profile ${aws_profile}

        Reference:

        - `Configure and use twine with CodeArtifact <https://docs.aws.amazon.com/codeartifact/latest/ug/python-configure-twine.html>`_
        - `AWS CodeArtifact CLI <https://docs.aws.amazon.com/cli/latest/reference/codeartifact/index.html>`_
        """
        aws_account_id = self.boto_ses_codeartifact.client("sts").get_caller_identity()[
            "Account"
        ]
        args = [
            f"{self.path_bin_aws}",
            "codeartifact",
            "login",
            "--tool",
            "twine",
            "--domain",
            self.aws_codeartifact_domain,
            "--domain-owner",
            aws_account_id,
            "--repository",
            self.aws_codeartifact_repository,
        ]
        if IS_CI is False:
            if self.aws_codeartifact_profile:
                args.extend(["--profile", self.aws_codeartifact_profile])

        print_command(args)
        if real_run:
            with temp_cwd(self.dir_project_root):
                subprocess.run(args, check=True)

    @logger.emoji_block(
        msg="Run twine upload command",
        emoji=Emoji.package,
    )
    def _twine_upload(
        self: "PyWf",
        real_run: bool = True,
        quiet: bool = False,
    ):
        """
        Upload Python package to CodeArtifact.

        Run

        .. code-block:: bash

            twine upload dist/* --repository codeartifact
        """
        aws_account_id = self.boto_ses_codeartifact.client("sts").get_caller_identity()[
            "Account"
        ]
        aws_region = self.aws_region
        console_url = (
            f"https://{aws_region}.console.aws.amazon.com/codesuite/codeartifact/d"
            f"/{aws_account_id}/{self.aws_codeartifact_domain}/r"
            f"/{self.aws_codeartifact_repository}/p/pypi/"
            f"{self.package_name}/versions?region={aws_region}"
        )
        logger.info(f"preview in AWS CodeArtifact console: {console_url}")
        args = [
            f"{self.path_bin_twine}",
            "upload",
            f"{self.dir_dist}/*",
            "--repository",
            "codeartifact",
        ]
        print_command(args)
        if real_run:
            with temp_cwd(self.dir_project_root):
                subprocess.run(args, check=True)

    def twine_upload(
        self: "PyWf",
        real_run: bool = True,
        verbose: bool = True,
    ):
        with logger.disabled(disable=not verbose):
            self._twine_upload(
                real_run=real_run,
                quiet=not verbose,
            )

    twine_upload.__doc__ = _twine_upload.__doc__

    @logger.emoji_block(
        msg="Publish Python package to AWS CodeArtifact",
        emoji=Emoji.package,
    )
    def _publish_to_codeartifact(
        self: "PyWf",
        real_run: bool = True,
        verbose: bool = True,
    ):
        """
        Publish your Python package to AWS CodeArtifact
        """
        try:
            self.codeartifact_client.describe_package_version(
                domain=self.aws_codeartifact_domain,
                repository=self.aws_codeartifact_repository,
                format="pypi",
                package=self.package_name_slug,
                packageVersion=self.package_version,
            )
            message = (
                f"package {self.package_name_slug!r} "
                f"= {self.package_version} already exists!"
            )
            raise Exception(message)
        except botocore.exceptions.ClientError as e:
            if e.response["Error"]["Code"] == "ResourceNotFoundException":
                pass
            else:
                raise e

        self.twine_authorization(real_run=real_run, verbose=verbose)
        with logger.nested():
            self.twine_upload(real_run=real_run, verbose=verbose)

    def publish_to_codeartifact(
        self: "PyWf",
        real_run: bool = True,
        verbose: bool = True,
    ):
        with logger.disabled(disable=not verbose):
            self._publish_to_codeartifact(
                real_run=real_run,
                verbose=verbose,
            )

    publish_to_codeartifact.__doc__ = _publish_to_codeartifact.__doc__

    @logger.emoji_block(
        msg="Remove package version from AWS CodeArtifact",
        emoji=Emoji.package,
    )
    def remove_from_codeartifact(
        self: "PyWf",
        real_run: bool = True,
        verbose: bool = True,
    ):
        """
        Remove a specific version of your Python package release AWS CodeArtifact.

        .. warning::

            I suggest don't do this unless you have to.
            If you re-publish your package with the same version,
            then you may need to invalid the poetry cache before doing poetry lock.
        """
        res = input(
            f"Are you sure you want to remove the package {self.package_name_slug!r} "
            f"version {self.package_version!r}? (Y/N): "
        )
        if res == "Y":
            if real_run:
                self.codeartifact_client.delete_package_versions(
                    domain=self.aws_codeartifact_domain,
                    repository=self.aws_codeartifact_repository,
                    format="pypi",
                    package=self.package_name_slug,
                    versions=[self.package_version],
                    expectedStatus="Published",
                )
                logger.info("Package version removed.")
            else:
                logger.info("Not a real run, do nothing.")
        else:
            logger.info("Aborted")
