"""
Key handling utilities for EC keys (ANSI X.62/RFC3279), domain parameter and
signatures.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from asn1crypto.algos import DSASignature
from asn1crypto.core import OctetString
from asn1crypto.keys import (
    ECDomainParameters,
    ECPrivateKey,
    NamedCurve,
    PublicKeyInfo,
)

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

if TYPE_CHECKING:
    from pkcs11.types import PublicKey


def encode_named_curve_parameters(oid: str) -> bytes:
    """
    Return DER-encoded ANSI X.62 EC parameters for a named curve.

    Curve names are given by object identifier or common name. Names come
    from `asn1crypto
    <https://github.com/wbond/asn1crypto/blob/master/asn1crypto/keys.py#L338>`_.

    :param str oid: OID or named curve
    :rtype: bytes
    """
    return ECDomainParameters(
        name="named",
        value=NamedCurve.unmap(oid),
    ).dump()


def decode_ec_public_key(
    der: bytes,
    encode_ec_point: bool = True,
) -> dict[Attribute, Any]:
    """
    Decode a DER-encoded EC public key as stored by OpenSSL into a dictionary
    of attributes able to be passed to :meth:`pkcs11.Session.create_object`.

    .. note:: **encode_ec_point**

        For use as an attribute `EC_POINT` should be DER-encoded (True).

        For key derivation implementations can vary.  Since v2.30 the
        specification says implementations MUST accept a raw `EC_POINT` for
        ECDH (False), however not all implementations follow this yet.

    :param bytes der: DER-encoded key
    :param encode_ec_point: See text.
    :rtype: dict(Attribute,*)
    """
    asn1 = PublicKeyInfo.load(der)

    assert asn1.algorithm == "ec", "Wrong algorithm, not an EC key!"

    ecpoint = bytes(asn1["public_key"])

    if encode_ec_point:
        ecpoint = OctetString(ecpoint).dump()

    return {
        Attribute.KEY_TYPE: KeyType.EC,
        Attribute.CLASS: ObjectClass.PUBLIC_KEY,
        Attribute.EC_PARAMS: asn1["algorithm"]["parameters"].dump(),
        Attribute.EC_POINT: ecpoint,
    }


def decode_ec_private_key(der: bytes) -> dict[Attribute, Any]:
    """
    Decode a DER-encoded EC private key as stored by OpenSSL into a dictionary
    of attributes able to be passed to :meth:`pkcs11.Session.create_object`.

    :param bytes der: DER-encoded key
    :rtype: dict(Attribute,*)
    """

    asn1 = ECPrivateKey.load(der)

    return {
        Attribute.KEY_TYPE: KeyType.EC,
        Attribute.CLASS: ObjectClass.PRIVATE_KEY,
        Attribute.EC_PARAMS: asn1["parameters"].chosen.dump(),
        Attribute.VALUE: asn1["private_key"].contents,
    }


def encode_ec_public_key(key: PublicKey) -> bytes:
    """
    Encode a DER-encoded EC public key as stored by OpenSSL.

    :param PublicKey key: EC public key
    :rtype: bytes
    """

    ecparams = ECDomainParameters.load(key[Attribute.EC_PARAMS])
    ecpoint = bytes(OctetString.load(key[Attribute.EC_POINT]))

    return PublicKeyInfo(
        {
            "algorithm": {
                "algorithm": "ec",
                "parameters": ecparams,
            },
            "public_key": ecpoint,
        }
    ).dump()


def encode_ecdsa_signature(signature: bytes) -> bytes:
    """
    Encode a signature (generated by :meth:`pkcs11.SignMixin.sign`) into
    DER-encoded ASN.1 (ECDSA_Sig_Value) format.

    :param bytes signature: signature as bytes
    :rtype: bytes
    """

    return DSASignature.from_p1363(signature).dump()


def decode_ecdsa_signature(der: bytes) -> bytes:
    """
    Decode a DER-encoded ASN.1 (ECDSA_Sig_Value) signature (as generated by
    OpenSSL/X.509) into PKCS #11 format.

    :param bytes der: DER-encoded signature
    :rtype bytes:
    """

    asn1 = DSASignature.load(der)
    return asn1.to_p1363()
