"""
Certificate handling utilities for X.509 (SSL) certificates.
"""

from __future__ import annotations

from typing import Any

from asn1crypto.core import OctetString
from asn1crypto.x509 import Certificate

from pkcs11.constants import Attribute, CertificateType, ObjectClass
from pkcs11.mechanisms import KeyType


def decode_x509_public_key(der: bytes) -> dict[Attribute, Any]:
    """
    Decode a DER-encoded X.509 certificate's public key into a set of
    attributes able to be passed to :meth:`pkcs11.Session.create_object`.

    For PEM-encoded certificates, use :func:`asn1crypto.pem.unarmor`.

    .. warning::

        Does not verify certificate.

    :param bytes der: DER-encoded certificate
    :rtype: dict(Attribute,*)
    """
    x509 = Certificate.load(der)
    key_info = x509.public_key
    key = bytes(key_info["public_key"])

    key_type = {
        "rsa": KeyType.RSA,
        "dsa": KeyType.DSA,
        "ec": KeyType.EC,
    }[key_info.algorithm]

    attrs: dict[Attribute, Any] = {
        Attribute.CLASS: ObjectClass.PUBLIC_KEY,
        Attribute.KEY_TYPE: key_type,
    }

    if key_type is KeyType.RSA:
        from .rsa import decode_rsa_public_key

        attrs.update(decode_rsa_public_key(key))
    elif key_type is KeyType.DSA:
        from .dsa import decode_dsa_domain_parameters, decode_dsa_public_key

        params = key_info["algorithm"]["parameters"].dump()

        attrs.update(decode_dsa_domain_parameters(params))
        attrs.update(
            {
                Attribute.VALUE: decode_dsa_public_key(key),
            }
        )
    elif key_type is KeyType.EC:
        params = key_info["algorithm"]["parameters"].dump()

        # bytes(key_info['public_key']) returns the binary encoding
        # of the EC point itself (decoded from its BitString representation in X.509),
        # but PKCS#11 expects this as a DER OctetString.
        key = OctetString(key).dump()
        attrs.update(
            {
                Attribute.EC_PARAMS: params,
                Attribute.EC_POINT: key,
            }
        )
    else:
        raise AssertionError("Should not be reached")

    return attrs


def decode_x509_certificate(
    der: bytes,
    extended_set: bool = False,
) -> dict[Attribute, Any]:
    """
    Decode a DER-encoded X.509 certificate into a dictionary of
    attributes able to be passed to :meth:`pkcs11.Session.create_object`.

    Optionally pass `extended_set` to include additional attributes:
    start date, end date and key identifiers.

    For PEM-encoded certificates, use :func:`asn1crypto.pem.unarmor`.

    .. warning::

        Does not verify certificate.

    :param bytes der: DER-encoded certificate
    :param extended_set: decodes more metadata about the certificate
    :rtype: dict(Attribute,*)
    """
    x509 = Certificate.load(der)
    subject = x509.subject
    issuer = x509.issuer
    serial = x509["tbs_certificate"]["serial_number"]

    template: dict[Attribute, Any] = {
        Attribute.CLASS: ObjectClass.CERTIFICATE,
        Attribute.CERTIFICATE_TYPE: CertificateType.X_509,
        Attribute.SUBJECT: subject.dump(),
        Attribute.ISSUER: issuer.dump(),
        Attribute.SERIAL_NUMBER: serial.dump(),
        Attribute.VALUE: x509.dump(),
    }

    if extended_set:
        start_date = x509["tbs_certificate"]["validity"]["not_before"].native.date()
        end_date = x509["tbs_certificate"]["validity"]["not_after"].native.date()

        template.update(
            {
                Attribute.START_DATE: start_date,
                Attribute.END_DATE: end_date,
            }
        )

        # FIXME: is this correct?
        try:
            template[Attribute.HASH_OF_SUBJECT_PUBLIC_KEY] = x509.key_identifier
        except KeyError:
            pass

        try:
            template[Attribute.HASH_OF_ISSUER_PUBLIC_KEY] = x509.authority_key_identifier
        except KeyError:
            pass

    return template
