from dataclasses import dataclass
from enum import Enum
from typing import Mapping, Union

from ..cose import COSECRV, COSEAlgorithmIdentifier
from ..exceptions import InvalidTPMPubAreaStructure

################
#
# A whole lotta domain knowledge is captured here, with hazy connections to source
# documents. Good places to start searching for more info on these values are the
# following Trusted Computing Group TPM Library docs linked in the WebAuthn API:
#
# - https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf
# - https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
# - https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf
#
################


class TPM_ST(str, Enum):
    RSP_COMMAND = "TPM_ST_RSP_COMMAND"
    NULL = "TPM_ST_NULL"
    NO_SESSIONS = "TPM_ST_NO_SESSIONS"
    SESSIONS = "TPM_ST_SESSIONS"
    ATTEST_NV = "TPM_ST_ATTEST_NV"
    ATTEST_COMMAND_AUDIT = "TPM_ST_ATTEST_COMMAND_AUDIT"
    ATTEST_SESSION_AUDIT = "TPM_ST_ATTEST_SESSION_AUDIT"
    ATTEST_CERTIFY = "TPM_ST_ATTEST_CERTIFY"
    ATTEST_QUOTE = "TPM_ST_ATTEST_QUOTE"
    ATTEST_TIME = "TPM_ST_ATTEST_TIME"
    ATTEST_CREATION = "TPM_ST_ATTEST_CREATION"
    CREATION = "TPM_ST_CREATION"
    VERIFIED = "TPM_ST_VERIFIED"
    AUTH_SECRET = "TPM_ST_AUTH_SECRET"
    HASHCHECK = "TPM_ST_HASHCHECK"
    AUTH_SIGNED = "TPM_ST_AUTH_SIGNED"
    FU_MANIFEST = "TPM_ST_FU_MANIFEST"


class TPM_ALG(str, Enum):
    ERROR = "TPM_ALG_ERROR"
    RSA = "TPM_ALG_RSA"
    SHA1 = "TPM_ALG_SHA1"
    HMAC = "TPM_ALG_HMAC"
    AES = "TPM_ALG_AES"
    MGF1 = "TPM_ALG_MGF1"
    KEYEDHASH = "TPM_ALG_KEYEDHASH"
    XOR = "TPM_ALG_XOR"
    SHA256 = "TPM_ALG_SHA256"
    SHA384 = "TPM_ALG_SHA384"
    SHA512 = "TPM_ALG_SHA512"
    NULL = "TPM_ALG_NULL"
    SM3_256 = "TPM_ALG_SM3_256"
    SM4 = "TPM_ALG_SM4"
    RSASSA = "TPM_ALG_RSASSA"
    RSAES = "TPM_ALG_RSAES"
    RSAPSS = "TPM_ALG_RSAPSS"
    OAEP = "TPM_ALG_OAEP"
    ECDSA = "TPM_ALG_ECDSA"
    ECDH = "TPM_ALG_ECDH"
    ECDAA = "TPM_ALG_ECDAA"
    SM2 = "TPM_ALG_SM2"
    ECSCHNORR = "TPM_ALG_ECSCHNORR"
    ECMQV = "TPM_ALG_ECMQV"
    KDF1_SP800_56A = "TPM_ALG_KDF1_SP800_56A"
    KDF2 = "TPM_ALG_KDF2"
    KDF1_SP800_108 = "TPM_ALG_KDF1_SP800_108"
    ECC = "TPM_ALG_ECC"
    SYMCIPHER = "TPM_ALG_SYMCIPHER"
    CAMELLIA = "TPM_ALG_CAMELLIA"
    CTR = "TPM_ALG_CTR"
    OFB = "TPM_ALG_OFB"
    CBC = "TPM_ALG_CBC"
    CFB = "TPM_ALG_CFB"
    ECB = "TPM_ALG_ECB"


class TPM_ECC_CURVE(str, Enum):
    NONE = "NONE"
    NIST_P192 = "NIST_P192"
    NIST_P224 = "NIST_P224"
    NIST_P256 = "NIST_P256"
    NIST_P384 = "NIST_P384"
    NIST_P521 = "NIST_P521"
    BN_P256 = "BN_P256"
    BN_P638 = "BN_P638"
    SM2_P256 = "SM2_P256"


class TPMCertInfoClockInfo:
    """
    10.11.1 TPMS_CLOCK_INFO
    """

    clock: bytes
    reset_count: int
    restart_count: int
    safe: bool

    def __init__(self, clock_info: bytes):
        self.clock = clock_info[0:8]
        self.reset_count = int.from_bytes(clock_info[8:12], "big")
        self.restart_count = int.from_bytes(clock_info[12:16], "big")
        self.safe = bool(clock_info[16])


class TPMCertInfoAttested:
    """
    10.12.3 TPMS_CERTIFY_INFO
    """

    name_alg: TPM_ALG
    name_alg_bytes: bytes
    name: bytes
    qualified_name: bytes

    def __init__(self, attested_name: bytes, qualified_name: bytes):
        self.name_alg = TPM_ALG_MAP[attested_name[0:2]]
        self.name_alg_bytes = attested_name[0:2]
        self.name = attested_name
        self.qualified_name = qualified_name


@dataclass
class TPMCertInfo:
    """
    10.12.8 TPMS_ATTEST
    """

    magic: bytes
    type: TPM_ST
    qualified_signer: bytes
    extra_data: bytes
    clock_info: TPMCertInfoClockInfo
    firmware_version: bytes
    attested: TPMCertInfoAttested


class TPMPubAreaParametersRSA:
    """
    12.2.3.5 TPMS_RSA_PARMS
    """

    symmetric: TPM_ALG
    scheme: TPM_ALG
    key_bits: bytes
    exponent: bytes

    def __init__(self, params: bytes):
        self.symmetric = TPM_ALG_MAP[params[0:2]]
        self.scheme = TPM_ALG_MAP[params[2:4]]
        self.key_bits = params[4:6]
        self.exponent = params[6:10]


class TPMPubAreaParametersECC:
    """
    12.2.3.6 TPMS_ECC_PARMS
    """

    symmetric: TPM_ALG
    scheme: TPM_ALG
    curve_id: TPM_ECC_CURVE
    kdf: TPM_ALG

    def __init__(self, params: bytes):
        self.symmetric = TPM_ALG_MAP[params[0:2]]
        self.scheme = TPM_ALG_MAP[params[2:4]]
        self.curve_id = TPM_ECC_CURVE_MAP[params[4:6]]
        self.kdf = TPM_ALG_MAP[params[6:8]]


class TPMPubAreaObjectAttributes:
    """
    8.3 TPMA_OBJECT (Object Attributes)
    """

    fixed_tpm: bool
    st_clear: bool
    fixed_parent: bool
    sensitive_data_origin: bool
    user_with_auth: bool
    admin_with_policy: bool
    no_da: bool
    encrypted_duplication: bool
    restricted: bool
    decrypt: bool
    sign_or_encrypt: bool

    def __init__(self, object_attributes: bytes):
        attrs = int.from_bytes(object_attributes[0:4], "big")
        self.fixed_tpm = attrs & (1 << 1) != 0
        self.st_clear = attrs & (1 << 2) != 0
        self.fixed_parent = attrs & (1 << 4) != 0
        self.sensitive_data_origin = attrs & (1 << 5) != 0
        self.user_with_auth = attrs & (1 << 6) != 0
        self.admin_with_policy = attrs & (1 << 7) != 0
        self.no_da = attrs & (1 << 10) != 0
        self.encrypted_duplication = attrs & (1 << 11) != 0
        self.restricted = attrs & (1 << 16) != 0
        self.decrypt = attrs & (1 << 17) != 0
        self.sign_or_encrypt = attrs & (1 << 18) != 0


class TPMPubAreaUnique:
    """
    12.2.3.2 TPMU_PUBLIC_ID
    """

    value: bytes

    def __init__(self, unique: bytes, alg_type: TPM_ALG):
        if alg_type == TPM_ALG.RSA:
            """
            As per 11.2.4.5 TPM2B_PUBLIC_KEY_RSA, extract `unique` of dynamic length
            """
            unique_length = int.from_bytes(unique[0:2], "big")
            rsa_unique = unique[2 : 2 + unique_length]
            self.value = rsa_unique
        elif alg_type == TPM_ALG.ECC:
            """
            As per 12.2.3.2 TPMU_PUBLIC_ID, `unique` is `TPMS_ECC_POINT` when `type`
            indicates ECC.

            As per 11.2.5.2 TPMS_ECC_POINT, `x` and `y` within are
            `TPM2B_ECC_PARAMETER`, which is a uint16 `size`, followed by bytes of
            the indicated length.

            `unique`, then, is structured thusly:
            [
                [x_len, ...x_bytes],
                [y_len, ...y_bytes]
            ]

            Get `unique` to this structure for easier comparison with the output from
            `decode_credential_public_key`, which will return bytes for its `x` and `y`
            and to which this value will be compared:

            [
                x_bytes,
                y_bytes,
            ]

            """
            pointer = 0
            unique_x_len = int.from_bytes(unique[0:2], "big")
            pointer += 2
            unique_x = unique[pointer : pointer + unique_x_len]
            pointer += unique_x_len
            unique_y_len = int.from_bytes(unique[pointer : pointer + 2], "big")
            pointer += 2
            unique_y = unique[pointer : pointer + unique_y_len]

            self.value = b"".join([unique_x, unique_y])
        else:
            raise InvalidTPMPubAreaStructure(
                f'Pub Area alg type "{alg_type}" was not "{TPM_ALG.RSA}" or "{TPM_ALG.ECC}"'
            )


@dataclass
class TPMPubArea:
    """
    12.2.4 TPMT_PUBLIC
    """

    type: TPM_ALG
    name_alg: TPM_ALG
    object_attributes: TPMPubAreaObjectAttributes
    auth_policy: bytes
    parameters: Union[TPMPubAreaParametersRSA, TPMPubAreaParametersECC]
    unique: TPMPubAreaUnique


@dataclass
class TPMManufacturerInfo:
    name: str
    id: str


"""
6.9 TPM_ST (Structure Tags)
"""
TPM_ST_MAP: Mapping[bytes, TPM_ST] = {
    b"\x00\xc4": TPM_ST.RSP_COMMAND,
    b"\x80\x00": TPM_ST.NULL,
    b"\x80\x01": TPM_ST.NO_SESSIONS,
    b"\x80\x02": TPM_ST.SESSIONS,
    b"\x80\x14": TPM_ST.ATTEST_NV,
    b"\x80\x15": TPM_ST.ATTEST_COMMAND_AUDIT,
    b"\x80\x16": TPM_ST.ATTEST_SESSION_AUDIT,
    b"\x80\x17": TPM_ST.ATTEST_CERTIFY,
    b"\x80\x18": TPM_ST.ATTEST_QUOTE,
    b"\x80\x19": TPM_ST.ATTEST_TIME,
    b"\x80\x1a": TPM_ST.ATTEST_CREATION,
    b"\x80\x21": TPM_ST.CREATION,
    b"\x80\x22": TPM_ST.VERIFIED,
    b"\x80\x23": TPM_ST.AUTH_SECRET,
    b"\x80\x24": TPM_ST.HASHCHECK,
    b"\x80\x25": TPM_ST.AUTH_SIGNED,
    b"\x80\x29": TPM_ST.FU_MANIFEST,
}


"""
6.3 TPM_ALG_ID
"""
TPM_ALG_MAP: Mapping[bytes, TPM_ALG] = {
    b"\x00\x00": TPM_ALG.ERROR,
    b"\x00\x01": TPM_ALG.RSA,
    b"\x00\x04": TPM_ALG.SHA1,
    b"\x00\x05": TPM_ALG.HMAC,
    b"\x00\x06": TPM_ALG.AES,
    b"\x00\x07": TPM_ALG.MGF1,
    b"\x00\x08": TPM_ALG.KEYEDHASH,
    b"\x00\x0a": TPM_ALG.XOR,
    b"\x00\x0b": TPM_ALG.SHA256,
    b"\x00\x0c": TPM_ALG.SHA384,
    b"\x00\x0d": TPM_ALG.SHA512,
    b"\x00\x10": TPM_ALG.NULL,
    b"\x00\x12": TPM_ALG.SM3_256,
    b"\x00\x13": TPM_ALG.SM4,
    b"\x00\x14": TPM_ALG.RSASSA,
    b"\x00\x15": TPM_ALG.RSAES,
    b"\x00\x16": TPM_ALG.RSAPSS,
    b"\x00\x17": TPM_ALG.OAEP,
    b"\x00\x18": TPM_ALG.ECDSA,
    b"\x00\x19": TPM_ALG.ECDH,
    b"\x00\x1a": TPM_ALG.ECDAA,
    b"\x00\x1b": TPM_ALG.SM2,
    b"\x00\x1c": TPM_ALG.ECSCHNORR,
    b"\x00\x1d": TPM_ALG.ECMQV,
    b"\x00\x20": TPM_ALG.KDF1_SP800_56A,
    b"\x00\x21": TPM_ALG.KDF2,
    b"\x00\x22": TPM_ALG.KDF1_SP800_108,
    b"\x00\x23": TPM_ALG.ECC,
    b"\x00\x25": TPM_ALG.SYMCIPHER,
    b"\x00\x26": TPM_ALG.CAMELLIA,
    b"\x00\x40": TPM_ALG.CTR,
    b"\x00\x41": TPM_ALG.OFB,
    b"\x00\x42": TPM_ALG.CBC,
    b"\x00\x43": TPM_ALG.CFB,
    b"\x00\x44": TPM_ALG.ECB,
}


"""
6.4 TPM_ECC_CURVE
"""
TPM_ECC_CURVE_MAP: Mapping[bytes, TPM_ECC_CURVE] = {
    b"\x00\x00": TPM_ECC_CURVE.NONE,
    b"\x00\x01": TPM_ECC_CURVE.NIST_P192,
    b"\x00\x02": TPM_ECC_CURVE.NIST_P224,
    b"\x00\x03": TPM_ECC_CURVE.NIST_P256,
    b"\x00\x04": TPM_ECC_CURVE.NIST_P384,
    b"\x00\x05": TPM_ECC_CURVE.NIST_P521,
    b"\x00\x10": TPM_ECC_CURVE.BN_P256,
    b"\x00\x11": TPM_ECC_CURVE.BN_P638,
    b"\x00\x20": TPM_ECC_CURVE.SM2_P256,
}


# Intentionally omit curves we can't map so a KeyError gets thrown
TPM_ECC_CURVE_COSE_CRV_MAP: Mapping[TPM_ECC_CURVE, COSECRV] = {
    TPM_ECC_CURVE.NIST_P256: COSECRV.P256,
    TPM_ECC_CURVE.NIST_P384: COSECRV.P384,
    TPM_ECC_CURVE.NIST_P521: COSECRV.P521,
    TPM_ECC_CURVE.BN_P256: COSECRV.P256,
    TPM_ECC_CURVE.SM2_P256: COSECRV.P256,
}


# Intentionally omit algs we can't map so a KeyError gets thrown
TPM_ALG_COSE_ALG_MAP: Mapping[TPM_ALG, COSEAlgorithmIdentifier] = {
    TPM_ALG.SHA256: COSEAlgorithmIdentifier.RSASSA_PSS_SHA_256,
    TPM_ALG.SHA384: COSEAlgorithmIdentifier.RSASSA_PKCS1_v1_5_SHA_384,
    TPM_ALG.SHA512: COSEAlgorithmIdentifier.RSASSA_PKCS1_v1_5_SHA_512,
    TPM_ALG.SHA1: COSEAlgorithmIdentifier.RSASSA_PKCS1_v1_5_SHA_1,
}


# Sourced from https://trustedcomputinggroup.org/resource/vendor-id-registry/
# Latest version: https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-Vendor-ID-Registry-Family-1.2-and-2.0-Version-1.07-Revision-0.02_pub.pdf
TPM_MANUFACTURERS: Mapping[str, TPMManufacturerInfo] = {
    "id:414D4400": TPMManufacturerInfo(name="AMD", id="AMD"),
    "id:414E5400": TPMManufacturerInfo(name="Ant Group", id="ANT"),
    "id:41544D4C": TPMManufacturerInfo(name="Atmel", id="ATML"),
    "id:4252434D": TPMManufacturerInfo(name="Broadcom", id="BRCM"),
    "id:4353434F": TPMManufacturerInfo(name="Cisco", id="CSCO"),
    "id:464C5953": TPMManufacturerInfo(name="Flyslice Technologies", id="FLYS"),
    "id:524F4343": TPMManufacturerInfo(name="Fuzhou Rockchip", id="ROCC"),
    "id:474F4F47": TPMManufacturerInfo(name="Google", id="GOOG"),
    "id:48504900": TPMManufacturerInfo(name="HPI", id="HPI"),
    "id:48504500": TPMManufacturerInfo(name="HPE", id="HPE"),
    "id:48495349": TPMManufacturerInfo(name="Huawei", id="HISI"),
    "id:49424d00": TPMManufacturerInfo(name="IBM", id="IBM"),
    "id:49465800": TPMManufacturerInfo(name="Infineon", id="IFX"),
    "id:494E5443": TPMManufacturerInfo(name="Intel", id="INTC"),
    "id:4C454E00": TPMManufacturerInfo(name="Lenovo", id="LEN"),
    "id:4D534654": TPMManufacturerInfo(name="Microsoft", id="MSFT"),
    "id:4E534D20": TPMManufacturerInfo(name="National Semiconductor", id="NSM"),
    "id:4E545A00": TPMManufacturerInfo(name="Nationz", id="NTZ"),
    "id:4E534700": TPMManufacturerInfo(name="NSING", id="NSG"),
    "id:4E544300": TPMManufacturerInfo(name="Nuvoton Technology", id="NTC"),
    "id:51434F4D": TPMManufacturerInfo(name="Qualcomm", id="QCOM"),
    "id:534D534E": TPMManufacturerInfo(name="Samsung", id="SMSN"),
    "id:53454345": TPMManufacturerInfo(name="SecEdge", id="SECE"),
    "id:534E5300": TPMManufacturerInfo(name="Sinosun", id="SNS"),
    "id:534D5343": TPMManufacturerInfo(name="SMSC", id="SMSC"),
    "id:53544D20": TPMManufacturerInfo(name="STMicroelectronics", id="STM"),
    "id:54584E00": TPMManufacturerInfo(name="Texas Instruments", id="TXN"),
    "id:57454300": TPMManufacturerInfo(name="Winbond", id="WEC"),
    "id:5345414C": TPMManufacturerInfo(name="Wisekey", id="SEAL"),
}
