import base64
import hashlib
import logging
from typing import Optional
from cryptography.fernet import Fernet, InvalidToken
from config import ENCRYPTION_KEY

logger = logging.getLogger("Crypto")


def derive_fernet_key(key_or_password: str) -> bytes:
    """
    Ensures a valid 32-byte url-safe base64 Fernet key.
    If the key is already a valid Fernet key, returns it as bytes.
    If the user supplied an arbitrary passphrase/password, derives a 32-byte key via SHA-256.
    """
    key_str = (key_or_password or "").strip()
    if not key_str:
        raise ValueError("A titkosítási feloldó kulcs nem lehet üres.")

    try:
        raw = base64.urlsafe_b64decode(key_str.encode("utf-8"))
        if len(raw) == 32:
            return key_str.encode("utf-8")
    except Exception:
        pass

    # Derive 32-byte key via SHA-256 and urlsafe base64 encode for Fernet
    digest = hashlib.sha256(key_str.encode("utf-8")).digest()
    return base64.urlsafe_b64encode(digest)


def get_cipher(custom_key: Optional[str] = None) -> Fernet:
    """Returns a Fernet cipher instance initialized with the master unlock key."""
    key = custom_key if custom_key is not None else ENCRYPTION_KEY
    fernet_key = derive_fernet_key(key)
    return Fernet(fernet_key)


def is_encrypted(val: Optional[str], custom_key: Optional[str] = None) -> bool:
    """
    Checks if a string is a valid encrypted Fernet token.
    Fernet tokens start with 'gAAAAA' and can be successfully decrypted.
    """
    if not val or not isinstance(val, str):
        return False
    val_clean = val.strip()
    if not val_clean.startswith("gAAAAA") or len(val_clean) < 60:
        return False
    try:
        cipher = get_cipher(custom_key)
        cipher.decrypt(val_clean.encode("utf-8"))
        return True
    except Exception:
        return False


def encrypt_value(val: Optional[str], custom_key: Optional[str] = None) -> Optional[str]:
    """
    Encrypts a plaintext string using the unlock key.
    If the string is already encrypted, returns it as-is to prevent double-encryption.
    """
    if val is None:
        return None
    val_str = str(val)
    if not val_str:
        return ""

    # Prevent double-encrypting
    if is_encrypted(val_str, custom_key):
        return val_str

    try:
        cipher = get_cipher(custom_key)
        return cipher.encrypt(val_str.encode("utf-8")).decode("utf-8")
    except Exception as e:
        logger.error(f"Hiba a titkosítás során: {e}")
        raise


def decrypt_value(val: Optional[str], custom_key: Optional[str] = None) -> Optional[str]:
    """
    Decrypts an encrypted string.
    If the string is legacy plaintext (not starting with gAAAAA), returns it as-is.
    If decryption fails, logs warning and returns the original string to prevent data loss.
    """
    if val is None:
        return None
    val_str = str(val)
    if not val_str:
        return ""

    val_clean = val_str.strip()
    if not val_clean.startswith("gAAAAA"):
        return val_str

    try:
        cipher = get_cipher(custom_key)
        return cipher.decrypt(val_clean.encode("utf-8")).decode("utf-8")
    except InvalidToken:
        logger.warning("Érvénytelen titkosítási token vagy hibás feloldó kulcs.")
        return val_str
    except Exception as e:
        logger.error(f"Hiba a feloldás során: {e}")
        return val_str
