"""外部系统凭据列加密。

与早期微信绑定不同，这里没有明文降级：未配置 master key 时拒绝创建和调用。
"""

from __future__ import annotations

import base64
import hashlib
import json
import os
import secrets

from cryptography.exceptions import InvalidTag
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

_LEGACY_PREFIX = "v1:"
_PREFIX = "v2:"
_ENV = "ZCBOT_CREDENTIAL_MASTER_KEY"
_KEY_ID_ENV = "ZCBOT_CREDENTIAL_KEY_ID"
_PREVIOUS_KEYS_ENV = "ZCBOT_CREDENTIAL_PREVIOUS_KEYS"


def configured() -> bool:
    return len(os.getenv(_ENV, "").strip()) >= 32


def _keyring() -> tuple[str, dict[str, str]]:
    current = os.getenv(_ENV, "").strip()
    if len(current) < 32:
        raise RuntimeError(f"{_ENV} 至少需要 32 个字符")
    current_id = os.getenv(_KEY_ID_ENV, "primary").strip() or "primary"
    if ":" in current_id or len(current_id) > 64:
        raise RuntimeError(f"{_KEY_ID_ENV} 格式无效")
    keys = {current_id: current}
    raw_previous = os.getenv(_PREVIOUS_KEYS_ENV, "").strip()
    if raw_previous:
        try:
            previous = json.loads(raw_previous)
        except ValueError as exc:
            raise RuntimeError(f"{_PREVIOUS_KEYS_ENV} 必须是 JSON 对象") from exc
        if not isinstance(previous, dict):
            raise RuntimeError(f"{_PREVIOUS_KEYS_ENV} 必须是 JSON 对象")
        for key_id, secret in previous.items():
            key_id = str(key_id).strip()
            secret = str(secret).strip()
            if not key_id or ":" in key_id or len(key_id) > 64 or len(secret) < 32:
                raise RuntimeError(f"{_PREVIOUS_KEYS_ENV} 包含无效密钥")
            keys.setdefault(key_id, secret)
    return current_id, keys


def _aes_key(secret: str) -> bytes:
    return hashlib.sha256(secret.encode("utf-8")).digest()


def encrypt_secret(value: str, *, aad: str = "") -> str:
    if not isinstance(value, str) or not value:
        raise ValueError("credential value must be a non-empty string")
    key_id, keys = _keyring()
    nonce = secrets.token_bytes(12)
    ciphertext = AESGCM(_aes_key(keys[key_id])).encrypt(
        nonce,
        value.encode("utf-8"),
        aad.encode("utf-8"),
    )
    encoded = base64.urlsafe_b64encode(nonce + ciphertext).decode("ascii")
    return f"{_PREFIX}{key_id}:{encoded}"


def decrypt_secret(value: str, *, aad: str = "") -> str:
    if not isinstance(value, str):
        raise RuntimeError("外部系统凭据格式无效")
    if value.startswith(_PREFIX):
        try:
            _, key_id, encoded = value.split(":", 2)
            _, keys = _keyring()
            secret = keys[key_id]
            payload = base64.urlsafe_b64decode(encoded.encode("ascii"))
            plaintext = AESGCM(_aes_key(secret)).decrypt(
                payload[:12],
                payload[12:],
                aad.encode("utf-8"),
            )
            return plaintext.decode("utf-8")
        except (KeyError, ValueError, InvalidTag, UnicodeDecodeError) as exc:
            raise RuntimeError(
                "外部系统凭据无法解密，密钥或绑定上下文可能已变化"
            ) from exc
    if not value.startswith(_LEGACY_PREFIX):
        raise RuntimeError("外部系统凭据格式无效")
    # 0027 前的 Fernet 密文没有 key id/AAD；仅用于滚动迁移时读取。
    try:
        _, keys = _keyring()
        for secret in keys.values():
            digest = hashlib.sha256(secret.encode("utf-8")).digest()
            try:
                return (
                    Fernet(base64.urlsafe_b64encode(digest))
                    .decrypt(value[len(_LEGACY_PREFIX) :].encode("ascii"))
                    .decode("utf-8")
                )
            except InvalidToken:
                continue
    except (RuntimeError, UnicodeDecodeError) as exc:
        raise RuntimeError("外部系统凭据无法解密，master key 可能已变化") from exc
    raise RuntimeError("外部系统凭据无法解密，master key 可能已变化")


def mask_username(username: str) -> str:
    username = (username or "").strip()
    if not username:
        return "***"
    if len(username) <= 2:
        return username[0] + "*"
    return username[:2] + "***" + username[-1:]
