"""passbook crypto models"""
from binascii import hexlify
from hashlib import md5
from typing import Optional
from uuid import uuid4

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import Certificate, load_pem_x509_certificate
from django.db import models
from django.utils.translation import gettext_lazy as _

from passbook.lib.models import CreatedUpdatedModel


class CertificateKeyPair(CreatedUpdatedModel):
    """CertificateKeyPair that can be used for signing or encrypting if `key_data`
    is set, otherwise it can be used to verify remote data."""

    kp_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)

    name = models.TextField()
    certificate_data = models.TextField(help_text=_("PEM-encoded Certificate data"))
    key_data = models.TextField(
        help_text=_(
            "Optional Private Key. If this is set, you can use this keypair for encryption."
        ),
        blank=True,
        default="",
    )

    _cert: Optional[Certificate] = None
    _private_key: Optional[RSAPrivateKey] = None
    _public_key: Optional[RSAPublicKey] = None

    @property
    def certificate(self) -> Certificate:
        """Get python cryptography Certificate instance"""
        if not self._cert:
            self._cert = load_pem_x509_certificate(
                self.certificate_data.encode("utf-8"), default_backend()
            )
        return self._cert

    @property
    def public_key(self) -> Optional[RSAPublicKey]:
        """Get public key of the private key"""
        if not self._public_key:
            self._public_key = self.private_key.public_key()
        return self._public_key

    @property
    def private_key(self) -> Optional[RSAPrivateKey]:
        """Get python cryptography PrivateKey instance"""
        if not self._private_key:
            self._private_key = load_pem_private_key(
                str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])),
                password=None,
                backend=default_backend(),
            )
        return self._private_key

    @property
    def fingerprint(self) -> str:
        """Get SHA256 Fingerprint of certificate_data"""
        return hexlify(self.certificate.fingerprint(hashes.SHA256()), ":").decode(
            "utf-8"
        )

    @property
    def kid(self):
        """Get Key ID used for JWKS"""
        return "{0}".format(
            md5(self.key_data.encode("utf-8")).hexdigest()  # nosec
            if self.key_data
            else ""
        )

    def __str__(self) -> str:
        return f"Certificate-Key Pair {self.name} {self.fingerprint}"

    class Meta:

        verbose_name = _("Certificate-Key Pair")
        verbose_name_plural = _("Certificate-Key Pairs")