from __future__ import annotations

import base64
import hashlib
import hmac
import os
import re
import secrets
from dataclasses import dataclass

from cryptography.hazmat.primitives.ciphers.aead import AESGCM


CERTIFICATE_AAD_KEY_ID_SIAFI_V1 = "NFSE_MASTER_KEY_SIAFI_AAD_V1"

def get_required_secret(name: str) -> str:
    value = os.environ.get(name, "")
    if not value:
        raise RuntimeError(f"Variavel obrigatoria ausente: {name}")
    return value


def load_master_key() -> bytes:
    raw = get_required_secret("NFSE_MASTER_KEY")
    try:
        key = base64.b64decode(raw, validate=True)
    except Exception as exc:
        raise RuntimeError("NFSE_MASTER_KEY deve estar em base64.") from exc
    if len(key) != 32:
        raise RuntimeError("NFSE_MASTER_KEY deve decodificar para 32 bytes.")
    return key


def encrypt_bytes(data: bytes, aad: bytes = b"") -> str:
    key = load_master_key()
    nonce = secrets.token_bytes(12)
    encrypted = AESGCM(key).encrypt(nonce, data, aad)
    return "v1:" + base64.b64encode(nonce + encrypted).decode("ascii")


def encrypt_blob(data: bytes, aad: bytes = b"") -> bytes:
    key = load_master_key()
    nonce = secrets.token_bytes(12)
    encrypted = AESGCM(key).encrypt(nonce, data, aad)
    return b"v1:" + base64.b64encode(nonce + encrypted)


def decrypt_bytes(payload: str | bytes, aad: bytes = b"") -> bytes:
    if isinstance(payload, bytes):
        payload_s = payload.decode("ascii")
    else:
        payload_s = payload
    if not payload_s.startswith("v1:"):
        raise ValueError("Payload criptografado invalido.")
    raw = base64.b64decode(payload_s[3:], validate=True)
    nonce, encrypted = raw[:12], raw[12:]
    return AESGCM(load_master_key()).decrypt(nonce, encrypted, aad)


def certificate_aad_siafi(codigo_siafi: str) -> bytes:
    codigo = codigo_siafi.strip()
    if not re.fullmatch(r"\d{1,10}", codigo):
        raise ValueError("Codigo SIAFI invalido para criptografia do certificado.")
    return f"nfse-certificado:siafi:{codigo}".encode("ascii")


def decrypt_certificate_value(
    payload: str | bytes,
    *,
    codigo_siafi: str,
    key_id: str | None,
) -> bytes:
    """Decrypts only certificates bound to the canonical SIAFI."""
    if key_id != CERTIFICATE_AAD_KEY_ID_SIAFI_V1:
        raise ValueError("Certificate must be registered again after the reset.")
    return decrypt_bytes(payload, aad=certificate_aad_siafi(codigo_siafi))


def sha256_hex(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def password_hash(password: str, salt: bytes | None = None) -> str:
    salt = salt or secrets.token_bytes(16)
    digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 200_000)
    return "pbkdf2_sha256$200000$%s$%s" % (
        base64.b64encode(salt).decode("ascii"),
        base64.b64encode(digest).decode("ascii"),
    )


def verify_password(password: str, encoded: str) -> bool:
    try:
        alg, rounds_s, salt_b64, digest_b64 = encoded.split("$", 3)
        if alg != "pbkdf2_sha256":
            return False
        salt = base64.b64decode(salt_b64)
        expected = base64.b64decode(digest_b64)
        digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, int(rounds_s))
        return hmac.compare_digest(digest, expected)
    except Exception:
        return False


@dataclass(frozen=True)
class SessionSigner:
    secret: bytes

    @classmethod
    def from_env(cls) -> "SessionSigner":
        secret = os.environ.get("NFSE_SESSION_SECRET") or os.environ.get("NFSE_MASTER_KEY", "")
        if not secret:
            raise RuntimeError("Configure NFSE_SESSION_SECRET ou NFSE_MASTER_KEY.")
        return cls(secret.encode("utf-8"))

    def sign(self, value: str) -> str:
        sig = hmac.new(self.secret, value.encode("utf-8"), hashlib.sha256).hexdigest()
        return f"{value}.{sig}"

    def unsign(self, signed: str) -> str | None:
        if "." not in signed:
            return None
        value, sig = signed.rsplit(".", 1)
        expected = hmac.new(self.secret, value.encode("utf-8"), hashlib.sha256).hexdigest()
        if not hmac.compare_digest(sig, expected):
            return None
        return value
