import os
import re
import uuid
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec


class Es512KeysManger:
    """
    Handles generation, loading, and saving of ES512 private and public keys.

    Methods:
        get_root_filename(self): Returns the root file name for keys.
        get_pub_cert(self): Returns the public key in PEM format.
        get_priv_cert(self): Returns the private key in PEM format.
        generate_new_keys(self): Generates new ES512 private and public keys.
        save_new_keys(self, cert_dir): Saves the generated keys to the specified directory.
        load_keys(self, cert_dir, public_only=True, filename=""): Loads the latest keys from the specified directory or specified keys in filename="".
    """

    def __init__(self) -> None:
        # primary file name root string
        self.__filename = uuid.uuid4().hex

        # __pem_privkey and __pem_pubkey are generated by this class
        self.__generated = False

        # pem keys (generated or loaded)
        self.__pem_privkey: str = None
        self.__pem_pubkey: str = None

    def get_root_filename(self) -> str:
        # Return root of filename keys
        return str(self.__filename)

    def get_pub_cert(self) -> str | None:
        # Return public key
        return self.__pem_pubkey

    def get_priv_cert(self) -> str | None:
        # Return private key
        return self.__pem_privkey

    def generate_new_keys(self) -> None:
        """
        Generate new ES512 keys
        """
        # Generate a private key for use in the exchange.
        privkey = ec.generate_private_key(ec.SECP521R1(), default_backend())

        # Serialize private key to PEM format
        self.__pem_privkey = privkey.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        )

        # Generate a public key from the private key
        public_key = privkey.public_key()

        # Serialize public key to PEM format
        self.__pem_pubkey = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo,
        )

        self.__generated = True

    def save_new_keys(self, cert_dir: str) -> bool:
        """
        Save generated keys to files

        Input:
            cert_dit: Direcory with keys
        """
        # protect save loaded certs
        if not self.__generated:
            return False

        try:
            priv_path = os.path.join(cert_dir, f"{self.__filename}.pem")
            pub_path = os.path.join(cert_dir, f"{self.__filename}-public.pem")

            # Save the public and private key to a files
            with open(priv_path, "wb") as f:
                f.write(self.__pem_privkey)

            with open(pub_path, "wb") as f:
                f.write(self.__pem_pubkey)

        except IOError as e:
            return False

        return True

    def load_keys(
        self, cert_dir: str, public_only: bool = True, filename: str = ""
    ) -> bool:
        """
        Load last keys to class

        Input:
            cert_dit: str Direcory with keys
            public_only: bool Set to False if you need load both keys
            filename: str If blank last created keys will be loaded
        """
        self.__generated = False
        try:
            # Ensure the directory exists
            if not os.path.isdir(cert_dir):
                return False

            if not filename:
                filename = self.__find_latest_key_filename(cert_dir)
                if not filename:
                    return False

            return self.__load_key_files(cert_dir, filename, public_only)

        except Exception:
            return False

    def __find_latest_key_filename(self, cert_dir: str) -> str | None:
        """
        Finds the latest key filename in the specified directory.

        Args:
            cert_dir (str): The directory to search for key files.

        Returns:
            str or None: The latest key filename or None if no keys are found.
        """
        try:
            pem_files = [f for f in os.listdir(cert_dir) if f.endswith(".pem")]
            pem_files.sort(
                key=lambda x: os.path.getmtime(os.path.join(cert_dir, x)), reverse=True
            )

            if pem_files:
                match = re.search(r"^([a-f0-9]{32})(-public)?\.pem$", pem_files[0])

                if match:
                    self.__filename = match.group(1)
                    return match.group(1)
                return ""
        except OSError:
            return None

    def __load_key_files(self, cert_dir: str, filename: str, public_only: bool) -> bool:
        """
        Loads key files from the specified directory.

        Args:
            cert_dir (str): The directory containing the key files.
            filename (str): The filename of the keys to load.
            public_only (bool): Whether to load only the public key.

        Returns:
            bool: True if keys are successfully loaded, False otherwise.

        Raises:
            IOError: If there is an issue reading the key files.
        """
        try:
            if not public_only:
                with open(os.path.join(cert_dir, f"{filename}.pem"), "r") as f:
                    self.__pem_privkey = f.read()

            with open(os.path.join(cert_dir, f"{filename}-public.pem"), "r") as f:
                self.__pem_pubkey = f.read()

            return True

        except IOError:
            # Log the IOError details here
            return False
