from abc import ABC

from enum import IntEnum
from dataclasses import dataclass, field
from typing import Dict, Set


class OpenSslEvpPkeyEnum(IntEnum):
    """Maps to the EVP_PKEY_XXX OpenSSL constants (obj_mac.h) used as the temporary key during key exchange."""

    DH = 28
    EC = 408
    X25519 = 1034
    X448 = 1035
    RSA = 6
    DSA = 116
    RSA_PSS = 912


class OpenSslEcNidEnum(IntEnum):
    """Maps to NID_XXX values valid for OpenSslEvpPkeyEnum.EC (obj_mac.h).

    Valid values for TLS taken from https://tools.ietf.org/html/rfc4492 and https://tools.ietf.org/html/rfc8422
    """

    # RFC4492 (now deprecated)
    SECT163K1 = 721
    SECT163R1 = 722
    SECT163R2 = 723
    SECT193R1 = 724
    SECT193R2 = 725
    SECT233K1 = 726
    SECT233R1 = 727
    SECT239K1 = 728
    SECT283K1 = 729
    SECT283R1 = 730
    SECT409K1 = 731
    SECT409R1 = 732
    SECT571K1 = 733
    SECT571R1 = 734
    SECP160K1 = 708
    SECP160R1 = 709
    SECP160R2 = 710
    SECP192K1 = 711
    SECP224K1 = 712
    SECP224R1 = 713
    SECP256K1 = 714

    # RFC8422 (current)
    SECP192R1 = 409
    SECP256R1 = 415
    SECP384R1 = 715
    SECP521R1 = 716
    X25519 = 1034
    X448 = 1035

    # RFC 7027: Brainpool curves for TLS v1.2
    # Only specific Brainpool curves are supported (ie. have a IANA name/ID) in TLS;
    #  see also : https://github.com/openssl/openssl/issues/9124
    # The brainpool curves have been deprecated in TLS 1.3
    brainpoolP256r1 = 927
    brainpoolP384r1 = 931
    brainpoolP512r1 = 933

    # These other brainpool NIDs cannot be used with TLS ie. SSL_get1_groups() (but can be used to sign something,
    #  for instance), so we don't make them available in nassl
    # brainpoolP160r1 = 921
    # brainpoolP160t1 = 922
    # brainpoolP192r1 = 923
    # brainpoolP192t1 = 924
    # brainpoolP224r1 = 925
    # brainpoolP224t1 = 926
    # brainpoolP256t1 = 928
    # brainpoolP320r1 = 929
    # brainpoolP320t1 = 930
    # brainpoolP384t1 = 932
    # brainpoolP512t1 = 934

    @classmethod
    def get_supported_by_ssl_client(cls) -> Set["OpenSslEcNidEnum"]:
        return {nid for nid in cls}


# Mapping between OpenSSL EVP_PKEY_XXX value and display name
_OPENSSL_EVP_PKEY_TO_NAME_MAPPING: Dict[OpenSslEvpPkeyEnum, str] = {
    OpenSslEvpPkeyEnum.DH: "DH",
    OpenSslEvpPkeyEnum.EC: "ECDH",
    OpenSslEvpPkeyEnum.X25519: "ECDH",
    OpenSslEvpPkeyEnum.X448: "ECDH",
    OpenSslEvpPkeyEnum.RSA: "RSA",
    OpenSslEvpPkeyEnum.DSA: "DSA",
    OpenSslEvpPkeyEnum.RSA_PSS: "RSA-PSS",
}


# Mapping between the OpenSSL NID_XXX value and the SECG name (https://www.rfc-editor.org/rfc/rfc8422.html#appendix-A)
_OPENSSL_NID_TO_SECG_ANSI_X9_62: Dict[OpenSslEcNidEnum, str] = {
    OpenSslEcNidEnum.SECT163K1: "sect163k1",
    OpenSslEcNidEnum.SECT163R1: "sect163r1",
    OpenSslEcNidEnum.SECT163R2: "sect163r2",
    OpenSslEcNidEnum.SECT193R1: "sect193r1",
    OpenSslEcNidEnum.SECT193R2: "sect193r2",
    OpenSslEcNidEnum.SECT233K1: "sect233k1",
    OpenSslEcNidEnum.SECT233R1: "sect233r1",
    OpenSslEcNidEnum.SECT239K1: "sect239k1",
    OpenSslEcNidEnum.SECT283K1: "sect283k1",
    OpenSslEcNidEnum.SECT283R1: "sect283r1",
    OpenSslEcNidEnum.SECT409K1: "sect409k1",
    OpenSslEcNidEnum.SECT409R1: "sect409r1",
    OpenSslEcNidEnum.SECT571K1: "sect571k1",
    OpenSslEcNidEnum.SECT571R1: "sect571r1",
    OpenSslEcNidEnum.SECP160K1: "secp160k1",
    OpenSslEcNidEnum.SECP160R1: "secp160r1",
    OpenSslEcNidEnum.SECP160R2: "secp160r2",
    OpenSslEcNidEnum.SECP192K1: "secp192k1",
    OpenSslEcNidEnum.SECP192R1: "secp192r1",
    OpenSslEcNidEnum.SECP224K1: "secp224k1",
    OpenSslEcNidEnum.SECP224R1: "secp224r1",
    OpenSslEcNidEnum.SECP256K1: "secp256k1",
    OpenSslEcNidEnum.SECP256R1: "secp256r1",
    OpenSslEcNidEnum.SECP384R1: "secp384r1",
    OpenSslEcNidEnum.SECP521R1: "secp521r1",
    OpenSslEcNidEnum.X25519: "X25519",
    OpenSslEcNidEnum.X448: "X448",
    OpenSslEcNidEnum.brainpoolP256r1: "brainpoolP256r1",
    OpenSslEcNidEnum.brainpoolP384r1: "brainpoolP384r1",
    OpenSslEcNidEnum.brainpoolP512r1: "brainpoolP512r1",
}


@dataclass(frozen=True)
class EphemeralKeyInfo(ABC):
    """Common fields shared by all kinds of TLS key exchanges."""

    type: OpenSslEvpPkeyEnum
    type_name: str = field(init=False)
    size: int
    public_bytes: bytearray

    def __post_init__(self) -> None:
        # Required because of frozen=True; https://docs.python.org/3/library/dataclasses.html#frozen-instances
        object.__setattr__(
            self,
            "type_name",
            _OPENSSL_EVP_PKEY_TO_NAME_MAPPING.get(self.type, "UNKNOWN"),
        )


@dataclass(frozen=True)
class EcDhEphemeralKeyInfo(EphemeralKeyInfo):
    curve: OpenSslEcNidEnum
    curve_name: str = field(init=False)

    def __post_init__(self) -> None:
        super().__post_init__()
        curve_name = _OPENSSL_NID_TO_SECG_ANSI_X9_62.get(self.curve, f"unknown-curve-with-openssl-id-{self.curve}")
        # Required because of frozen=True; https://docs.python.org/3/library/dataclasses.html#frozen-instances
        object.__setattr__(self, "curve_name", curve_name)


@dataclass(frozen=True)
class NistEcDhKeyExchangeInfo(EcDhEphemeralKeyInfo):
    x: bytearray
    y: bytearray


@dataclass(frozen=True)
class DhEphemeralKeyInfo(EphemeralKeyInfo):
    prime: bytearray
    generator: bytearray
