import base64
import binascii
import json
import os
import time
from typing import Any, Dict

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

from app.core.config import settings


def _resolve_encryption_key() -> bytes:
    raw = settings.IDENTITY_QR_ENCRYPTION_KEY
    if not raw or not str(raw).strip():
        raise ValueError("IDENTITY_QR_ENCRYPTION_KEY is empty; set it in config or env")
    s = str(raw).strip()
    if len(s) == 64:
        try:
            key = bytes.fromhex(s)
            if len(key) == 32:
                return key
        except (ValueError, binascii.Error):
            pass
    try:
        pad = "=" * (-len(s) % 4)
        key = base64.urlsafe_b64decode(s + pad)
        if len(key) == 32:
            return key
    except Exception:
        pass
    b = s.encode("utf-8")
    if len(b) == 32:
        return b
    raise ValueError(
        "IDENTITY_QR_ENCRYPTION_KEY must be 32 bytes: 64-char hex, base64, or UTF-8 length 32"
    )


def encrypt_identity_payload(payload: Dict[str, Any]) -> str:
    key = _resolve_encryption_key()
    aes = AESGCM(key)
    iv = os.urandom(12)
    pt = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
    ct = aes.encrypt(iv, pt, None)
    return base64.urlsafe_b64encode(iv + ct).decode("ascii")


def decrypt_identity_token(token: str) -> Dict[str, Any]:
    s = token.strip()
    pad = "=" * (-len(s) % 4)
    try:
        raw = base64.urlsafe_b64decode(s + pad)
    except Exception as e:
        raise ValueError("invalid token encoding") from e
    if len(raw) < 12 + 16:
        raise ValueError("token too short")
    iv, ct = raw[:12], raw[12:]
    key = _resolve_encryption_key()
    aes = AESGCM(key)
    try:
        pt = aes.decrypt(iv, ct, None)
    except InvalidTag:
        raise ValueError("invalid token or tampered data") from None
    return json.loads(pt.decode("utf-8"))


def assert_payload_fresh(payload: Dict[str, Any]) -> None:
    exp = payload.get("exp")
    if exp is None:
        raise ValueError("missing exp")
    if time.time() > float(exp):
        raise ValueError("identity code expired")
