"""
Module used for defining the certificate class and it's functionality.
"""

import sys
import random
import string
from datetime import datetime, timedelta
from typing import Union
from os.path import abspath, dirname, join
from cryptography.hazmat.primitives.asymmetric import rsa, ec
from cryptography.hazmat.primitives import serialization
from .SimUtils import hash_info

script_dir = dirname(abspath(__file__))

if script_dir in ['PKI_Practice', 'PKI Practice', 'app']:
    sys.path.append(abspath(script_dir))
elif script_dir == 'PKIPractice':
    sys.path.append(abspath(join(script_dir, '..')))
else:
    sys.path.append(abspath(join(script_dir, '../..')))

from PKIPractice.Utilities.DataclassUtils import *


class PKICertificate:
    """
    The PKICertificate class represents a given certificate that can be sent and held by holders.

    Attributes:
        name (str): The name of the certificate, derived from the `subject_name` parameter.
        subject_info (SUBJECT_INFO): Information about the subject, including common name, country,
            state, organization, and contact details.
        issuer_info (ISSUER_INFO): Information about the certificate issuer, such as common name,
            country, state, and organization details.
        serial_number (str): A unique 16-character hexadecimal string generated for the certificate.
        x509_version (int): The X.509 version of the certificate, defaulting to version 3.
        signature_alg (str): The signature algorithm used for signing the certificate, derived from
            `subject_env_info.sig_hash`.
        valid_start (datetime): The datetime when the certificate becomes valid, initialized to the
            current time.
        valid_end (Union[datetime, None]): The datetime when the certificate expires. If
            `subject_env_info.cert_valid_dur` is `'none'`, this is `None`. Otherwise, it is computed
            based on the duration string (formatted as `HH:MM:SS`).
        encryption_alg (dict): Details of the encryption algorithm used in the certificate, derived
            from `subject_env_info.encrypt_alg`.
        subject_pub_key (Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey]): The subject's public key,
            which can be either an RSA or elliptic curve public key.
        signature (str): The certificate's digital signature, generated by hashing the certificate
            content using the specified `signature_alg`.

    Methods:
        hash_content() -> str:
            Returns the core attributes of the certificate as a string.
    """
    def __init__(self, subject_name: str, subject_info: HOLDER_INFO,
                 issuer_info: HOLDER_INFO, subject_env_info: HOLDER_ENV_INFO,
                 subject_pub_key: Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey]):

        # Name
        self.name = subject_name

        # Subject info
        self.subject_info: SUBJECT_INFO = SUBJECT_INFO(
            common_name=subject_info.common_name, country=subject_info.country, state=subject_info.state,
            local=subject_info.local, org=subject_info.org, org_unit=subject_info.org_unit, email=subject_info.email,
            url=subject_info.url
        )

        # Issuer info
        self.issuer_info: ISSUER_INFO = ISSUER_INFO(
            common_name=issuer_info.common_name, country=issuer_info.country, state=issuer_info.state,
            local=issuer_info.local, org=issuer_info.org, url=issuer_info.url
        )

        # Certificate information
        self.serial_number: str = ''.join(random.choices(string.hexdigits[:16], k=16)).upper()
        self.x509_version: int = 3
        self.signature_alg: str = subject_env_info.sig_hash

        # Time information
        self.valid_start: datetime = datetime.now()
        if subject_env_info.cert_valid_dur == 'none':
            self.valid_end: Union[datetime, None] = None
        else:
            hours, minutes, seconds = map(int, subject_env_info.cert_valid_dur.split(":"))
            delta = timedelta(hours=hours, minutes=minutes, seconds=seconds)
            self.valid_end: Union[datetime, None] = self.valid_start + delta

        # Asymmetric Key information
        self.encryption_alg: dict = subject_env_info.encrypt_alg
        self.subject_pub_key: Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey] = subject_pub_key

        self.signature: str = hash_info(self.hash_content, self.signature_alg)

    @property
    def hash_content(self) -> str:
        """
        Returns the core attributes of the certificate as a hashable string.

        Returns:
            str: The core attributes turned into string and lined up.
        """

        subject_pub_pem = self.subject_pub_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo,
        )

        return f'{self.name}{self.subject_info}{self.issuer_info}' \
               f'{self.serial_number}{self.x509_version}{self.signature_alg}' \
               f'{self.valid_start}{self.valid_end}{self.encryption_alg}{subject_pub_pem}'
