import typing as t
from collections.abc import KeysView
from abc import ABCMeta, abstractmethod
from .types import DictKey, AnyKey, KeyParameters
from .._rfc7638 import calculate_thumbprint
from .._rfc9278 import concat_thumbprint_uri
from ..registry import (
    KeyParameterRegistryDict,
    JWK_PARAMETER_REGISTRY,
    KeyOperationRegistryDict,
    JWK_OPERATION_REGISTRY,
)
from ..util import to_bytes
from ..errors import (
    KeyParameterError,
    UnsupportedKeyUseError,
    UnsupportedKeyAlgorithmError,
    UnsupportedKeyOperationError,
)


NativePrivateKey = t.TypeVar("NativePrivateKey")
NativePublicKey = t.TypeVar("NativePublicKey")
GenericKey = t.TypeVar("GenericKey", bound="BaseKey[t.Any, t.Any]")


class NativeKeyBinding(metaclass=ABCMeta):
    use_key_ops_registry: t.ClassVar[dict[str, list[str]]] = {
        "sig": ["sign", "verify"],
        "enc": ["encrypt", "decrypt", "wrapKey", "unwrapKey", "deriveKey", "deriveBits"],
    }

    @classmethod
    @abstractmethod
    def convert_raw_key_to_dict(cls, raw_key: t.Any, private: bool) -> DictKey:
        pass

    @classmethod
    @abstractmethod
    def import_from_dict(cls, value: DictKey) -> t.Any:
        pass

    @classmethod
    @abstractmethod
    def import_from_bytes(cls, value: bytes, password: t.Any = None) -> t.Any:
        pass

    @staticmethod
    def as_bytes(
        key: GenericKey,
        encoding: t.Literal["PEM", "DER"] | None = None,
        private: bool = False,
        password: str | None = None,
    ) -> bytes:
        raise NotImplementedError()

    @classmethod
    def validate_dict_key_registry(cls, dict_key: DictKey, registry: KeyParameterRegistryDict) -> None:
        for k in registry:
            if registry[k].required and k not in dict_key:
                raise KeyParameterError(f"'{k}' is required")

            if k in dict_key:
                try:
                    registry[k].validate(dict_key[k])
                except ValueError as error:
                    raise KeyParameterError(f"'{k}' {error}")

    @classmethod
    def validate_dict_key_use_operations(cls, dict_key: DictKey) -> None:
        if "use" in dict_key and "key_ops" in dict_key:
            _use: str = dict_key["use"]  # type: ignore
            operations = cls.use_key_ops_registry[_use]
            for op in dict_key["key_ops"]:
                if op not in operations:
                    raise KeyParameterError("'use' and 'key_ops' does not match")


class BaseKey(t.Generic[NativePrivateKey, NativePublicKey], metaclass=ABCMeta):
    key_type: t.ClassVar[str]
    binding: t.ClassVar[type[NativeKeyBinding]]
    value_registry: t.ClassVar[KeyParameterRegistryDict]
    param_registry: t.ClassVar[KeyParameterRegistryDict] = JWK_PARAMETER_REGISTRY
    operation_registry: t.ClassVar[KeyOperationRegistryDict] = JWK_OPERATION_REGISTRY
    thumbprint_digest_method: t.Literal["sha256", "sha384", "sha512"] = "sha256"

    def __init__(
        self,
        raw_value: NativePrivateKey | NativePublicKey,
        original_value: t.Any,
        parameters: KeyParameters | None = None,
    ):
        self._raw_value = raw_value
        self.original_value = original_value
        self.extra_parameters = parameters
        self._dict_value: DictKey = {}
        if isinstance(original_value, dict):
            if parameters is not None:
                data = {**original_value, **parameters, "kty": self.key_type}
            else:
                data = {**original_value, "kty": self.key_type}
            self.validate_dict_key(data)
            self._dict_value = data

    def __eq__(self, other: t.Any) -> bool:
        if not isinstance(other, self.__class__):
            return False
        return self.dict_value == other.dict_value

    def keys(self) -> KeysView[str]:
        return self.dict_value.keys()

    def __getitem__(self, k: str) -> str | list[str]:
        return self.dict_value[k]

    def get(self, k: str, default: str | list[str] | None = None) -> str | list[str] | None:
        return self.dict_value.get(k, default)

    def ensure_kid(self) -> None:
        """Ensure this key has a ``kid``. If ``kid`` is not provided by default,
        it will generate the kid with ``.thumbprint`` method, which is defined
        by RFC7638."""
        if "kid" not in self.dict_value:
            self._dict_value["kid"] = self.thumbprint()

    @property
    def kid(self) -> str | None:
        """The "kid" value of the JSON Web Key."""
        return t.cast(t.Optional[str], self.get("kid"))

    @property
    def alg(self) -> str | None:
        """The "alg" value of the JSON Web Key."""
        return t.cast(t.Optional[str], self.get("alg"))

    @property
    def raw_value(self) -> t.Any:
        raise NotImplementedError()

    @property
    def is_private(self) -> bool:
        raise NotImplementedError()

    @property
    def dict_value(self) -> DictKey:
        """Property of the Key in Dict (JSON)."""
        if self._dict_value:
            return self._dict_value

        data = self.binding.convert_raw_key_to_dict(self.raw_value, self.is_private)
        if self.extra_parameters is not None:
            data.update(self.extra_parameters)  # type: ignore
        data["kty"] = self.key_type
        self.validate_dict_key(data)
        self._dict_value = data
        return data

    @property
    def public_key(self) -> NativePublicKey:
        raise NotImplementedError()

    @property
    def private_key(self) -> NativePrivateKey | None:
        raise NotImplementedError()

    def thumbprint(self) -> str:
        """Call this method will generate the thumbprint with algorithm
        defined in RFC7638."""
        fields = [k for k in self.value_registry if self.value_registry[k].required]
        fields.append("kty")
        data = {key: self.dict_value[key] for key in fields}
        return calculate_thumbprint(data, self.thumbprint_digest_method)

    def thumbprint_uri(self) -> str:
        """Call this method will generate the thumbprint URI
        defined in RFC9278."""
        value = self.thumbprint()
        return concat_thumbprint_uri(value, self.thumbprint_digest_method)

    def as_dict(self, private: bool = False, **params: t.Any) -> DictKey:
        """Output this key to a JWK format (in dict). By default, it will return
        the ``dict_value`` of this key.

        :param private: determine whether this method should output private key or not
        :param params: other parameters added into this key
        :raise: ValueError
        """
        # check private conflicts
        if private and not self.is_private:
            raise ValueError("This key is not a private key.")

        data = self.dict_value.copy()
        if private:
            data.update(params)
            return data

        # clear private fields
        for k in self.dict_value:
            if k in self.value_registry and self.value_registry[k].private:
                del data[k]

        data.update(params)
        return data

    def check_use(self, use: str) -> None:
        """Check if this key supports the given "use".

        Values defined by this specification are:

        - "sig" (signature)
        - "enc" (encryption)

        Other values MAY be used.  The "use" value is a case-sensitive
        string. Use of the "use" member is OPTIONAL, unless the application
        requires its presence.

        :param use: this key is used for, e.g. "sig", "enc"
        :raise UnsupportedKeyUseError: if this key is not designed for the given use
        """
        designed_use = self.get("use")
        if designed_use and designed_use != use:
            raise UnsupportedKeyUseError(f"This key is designed to be used for '{designed_use}'")

    def check_alg(self, alg: str) -> None:
        """Check if this key supports the given "alg".

        :param alg: the algorithm this key is intended to be used, e.g. "HS256", "ECDH-EC"
        :raise UnsupportedKeyAlgorithmError: if this key is not designed for the given algorithm
        """
        designed_alg = self.get("alg")
        if designed_alg and designed_alg != alg:
            raise UnsupportedKeyAlgorithmError(f"This key is designed for algorithm '{designed_alg}'")

    def check_key_op(self, operation: str) -> None:
        """Check if the given key_op is supported by this key.

        :param operation: key operation value, such as "sign", "encrypt".
        :raise UnsupportedKeyOperationError: if the operation is not supported by this key.
        """
        key_ops = self.get("key_ops")
        if key_ops is not None and operation not in key_ops:
            raise UnsupportedKeyOperationError(f"Unsupported key_op '{operation}'")

        assert operation in self.operation_registry
        reg = self.operation_registry[operation]
        if reg.private and not self.is_private:
            raise UnsupportedKeyOperationError(f"Invalid key_op '{operation}' for public key")

    @t.overload
    def get_op_key(self, operation: t.Literal["verify", "encrypt", "wrapKey", "deriveKey"]) -> NativePublicKey: ...

    @t.overload
    def get_op_key(self, operation: t.Literal["sign", "decrypt", "unwrapKey"]) -> NativePrivateKey: ...

    def get_op_key(self, operation: str) -> NativePublicKey | NativePrivateKey:
        self.check_key_op(operation)
        reg = self.operation_registry[operation]
        if reg.private:
            assert self.private_key is not None
            return self.private_key
        return self.public_key

    @classmethod
    def validate_dict_key(cls, data: DictKey) -> None:
        cls.binding.validate_dict_key_registry(data, cls.param_registry)
        cls.binding.validate_dict_key_registry(data, cls.value_registry)
        cls.binding.validate_dict_key_use_operations(data)

    @classmethod
    def import_key(
        cls: type[GenericKey],
        value: AnyKey,
        parameters: KeyParameters | None = None,
        password: t.Any = None,
    ) -> GenericKey:
        if isinstance(value, dict):
            cls.validate_dict_key(value)
            raw_key = cls.binding.import_from_dict(value)
            return cls(raw_key, value, parameters)

        raw_key = cls.binding.import_from_bytes(to_bytes(value), password)
        return cls(raw_key, value, parameters)

    @classmethod
    def generate_key(
        cls: type[GenericKey],
        *,
        parameters: KeyParameters | None = None,
        private: bool = True,
        auto_kid: bool = False,
    ) -> GenericKey:
        raise NotImplementedError()


class SymmetricKey(BaseKey[bytes, bytes], metaclass=ABCMeta):
    @property
    def raw_value(self) -> bytes:
        """The raw key in bytes."""
        return self._raw_value

    @property
    def is_private(self) -> bool:
        """A symmetric key will always be private."""
        return True

    @property
    def public_key(self) -> bytes:
        """Returns the ``raw_value`` as the public key."""
        return self.raw_value

    @property
    def private_key(self) -> bytes:
        """Returns the ``raw_value`` as the private key."""
        return self.raw_value


class AsymmetricKey(BaseKey[NativePrivateKey, NativePublicKey], metaclass=ABCMeta):
    @property
    def raw_value(self) -> t.Union[NativePublicKey, NativePrivateKey]:
        return self._raw_value

    def as_bytes(
        self,
        encoding: t.Literal["PEM", "DER"] | None = None,
        private: bool = False,
        password: str | None = None,
    ) -> bytes:
        return self.binding.as_bytes(self, encoding, private, password)

    def as_pem(self, private: bool = False, password: str | None = None) -> bytes:
        return self.as_bytes(private=private, password=password)

    def as_der(self, private: bool = False, password: str | None = None) -> bytes:
        return self.as_bytes(encoding="DER", private=private, password=password)


class CurveKey(AsymmetricKey[NativePrivateKey, NativePublicKey]):
    @property
    @abstractmethod
    def curve_name(self) -> str:
        pass

    @abstractmethod
    def exchange_derive_key(self, key: t.Any) -> bytes:
        pass
