import logging
import os
from typing import Any, Dict, List, Optional  # noqa: F401

from uaclient import exceptions, http, secret_manager, system, util
from uaclient.clouds import PublicCloudAutoAttachInstance

LOG = logging.getLogger(util.replace_top_level_logger_name(__name__))

TOKEN_URL = (
    "http://metadata/computeMetadata/v1/instance/service-accounts/"
    "default/identity?audience=contracts.canonical.com&"
    "format=full&licenses=TRUE"
)
LICENSES_URL = (
    "http://metadata.google.internal/computeMetadata/v1/instance/licenses/"
    "?recursive=true"
)
WAIT_FOR_CHANGE = "&wait_for_change=true"
LAST_ETAG = "&last_etag={etag}"

DMI_PRODUCT_NAME = "/sys/class/dmi/id/product_name"
GCP_PRODUCT_NAME = "Google Compute Engine"

# Maps Ubuntu LTS releases to their corresponding GCP license IDs. Used by the
# client at boot time to detect in-place upgrades from regular Ubuntu LTS to
# Ubuntu Pro on GCP. The client checks instance metadata against these IDs to
# confirm if a Pro license has been appended. If a release is missing from this
# dict, users on regular GCP instances will not be able to perform in-place
# upgrades to Pro for that release.
#
# NOTE: Append license IDs of every new LTS prior to release!
GCP_LICENSES = (
    {  # [0] Base, [1] Minimal, [2] Base Marketplace, [3] Minimal Marketplace
        "xenial": ["8045211386737108299"],
        "bionic": [
            "6022427724719891830",
            "7427070211152946628",
            "5038396554373364947",
            "6800391258104804724",
        ],
        "focal": [
            "599959289349842382",
            "7818678586103571931",
            "1160717088167951565",
            "3205608531634042128",
        ],
        "jammy": [
            "2592866803419978320",
            "160084055690847662",
            "2161143262079483057",
            "3217217922724410098",
        ],
        "noble": [
            "2176054482269786025",
            "4168323874790319525",
            "5097486993533230500",
            "7478836627327038165",
        ],
        "resolute": [
            "9208223505600398646",
            "94031624764048077",
            "3894136194827958490",
            "2149234954061887532",
        ],
    }
)


class GCPAutoAttachInstance(PublicCloudAutoAttachInstance):
    def __init__(self):
        # store ETAG
        # https://cloud.google.com/compute/docs/metadata/querying-metadata#etags  # noqa
        self.etag = None  # type: Optional[str]

    # mypy does not handle @property around inner decorators
    # https://github.com/python/mypy/issues/1362
    @property  # type: ignore
    @util.retry(exceptions.CloudMetadataError, retry_sleeps=[0.5, 1, 1])
    def identity_doc(self) -> Dict[str, Any]:
        response = http.readurl(
            TOKEN_URL, headers={"Metadata-Flavor": "Google"}, timeout=1
        )
        if response.code == 200:
            secret_manager.secrets.add_secret(response.body)
            return {"identityToken": response.body}

        error_desc = response.json_dict.get("error_description")
        if error_desc and "service account" in error_desc.lower():
            raise exceptions.GCPServiceAccountError(
                status_code=response.code, error_msg=error_desc
            )
        raise exceptions.CloudMetadataError(
            code=response.code, body=response.body
        )

    @property
    def cloud_type(self) -> str:
        return "gcp"

    @property
    def is_viable(self) -> bool:
        """This machine is a viable GCPInstance"""
        if os.path.exists(DMI_PRODUCT_NAME):
            product_name = system.load_file(DMI_PRODUCT_NAME)
            if GCP_PRODUCT_NAME == product_name.strip():
                return True

        return False

    def should_poll_for_pro_license(self) -> bool:
        series = system.get_release_info().series
        if series not in GCP_LICENSES:
            LOG.info("This series isn't supported for GCP auto-attach.")
            return False
        return True

    def is_pro_license_present(self, *, wait_for_change: bool) -> bool:
        url = LICENSES_URL

        if wait_for_change:
            url += WAIT_FOR_CHANGE
            if self.etag:
                url += LAST_ETAG.format(etag=self.etag)

        response = http.readurl(url, headers={"Metadata-Flavor": "Google"})
        if response.code == 200:
            license_ids = [license["id"] for license in response.json_list]
            self.etag = response.headers.get("etag")
            series = system.get_release_info().series
            for lic in GCP_LICENSES.get(series, []):
                if lic in license_ids:
                    return True
            return False

        LOG.error(response.body)
        if response.code == 400:
            raise exceptions.CancelProLicensePolling()
        else:
            raise exceptions.DelayProLicensePolling()
