import secrets
from pathlib import Path
from typing import Union

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes


# ===== Crypto Utilities =====
class CryptoHelper:
    """Cryptographic utilities for wafer data encryption/decryption."""

    # ===== Hex/Byte Conversion =====
    @staticmethod
    def hex_string_to_bytearray(hex_str: str) -> bytes:
        """Converts a hex string to bytes (e.g., '48656C6C6F' → b'Hello')."""
        if len(hex_str) % 2 != 0:
            raise ValueError("Hex string must have even length")
        try:
            return bytes.fromhex(hex_str)
        except ValueError as e:
            raise ValueError(f"Invalid hex string: {e}")

    @staticmethod
    def bytearray_to_hex_string(data: Union[bytes, bytearray]) -> str:
        """Converts bytes to lowercase hex string."""
        return data.hex()

    # ===== Random Data =====
    @staticmethod
    def generate_random_bytes(length: int) -> bytes:
        """Generates cryptographically secure random bytes."""
        if length < 0:
            raise ValueError("Length must be non-negative")
        return secrets.token_bytes(length)

    # ===== AES Decryption =====
    @staticmethod
    def decrypt_aes(data: bytes, key: bytes, iv: bytes) -> bytes:
        """
        Decrypts AES-128-CBC data with PKCS7 padding.

        Args:
            data: Encrypted ciphertext (must be multiple of 16 bytes)
            key: 16-byte AES key
            iv: 16-byte IV

        Returns:
            Decrypted plaintext

        Raises:
            ValueError: If inputs are invalid
            cryptography.exceptions.InvalidSignature: On decryption failure
        """
        if len(key) != 16:
            raise ValueError("Key must be 16 bytes (AES-128)")
        if len(iv) != 16:
            raise ValueError("IV must be 16 bytes")
        if len(data) == 0:
            return b""
        if len(data) % 16 != 0:
            raise ValueError("Ciphertext length must be multiple of 16 bytes")

        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        decrypt_ctx = cipher.decryptor()
        plaintext_padded = decrypt_ctx.update(data) + decrypt_ctx.finalize()

        # Remove PKCS7 padding
        pad_len = plaintext_padded[-1]
        if pad_len > 16 or pad_len == 0:
            raise ValueError("Invalid padding")
        return plaintext_padded[:-pad_len]

    # ===== Binary File I/O =====
    @staticmethod
    def save_to_binary_file(file_path: Union[str, Path], data: bytes) -> None:
        """Writes bytes to a binary file."""
        Path(file_path).write_bytes(data)

    @staticmethod
    def read_from_binary_file(file_path: Union[str, Path]) -> bytes:
        """Reads bytes from a binary file."""
        return Path(file_path).read_bytes()

    # ===== Convenience Helpers =====
    @classmethod
    def read_hex_string_from_binary_file(cls, file_path: Union[str, Path]) -> str:
        """Reads binary file and returns hex string."""
        data = cls.read_from_binary_file(file_path)
        return cls.bytearray_to_hex_string(data)
