"""凭据管理器。

使用 AES-256-GCM 加密存储 CLI 登录凭据到 ~/.hezor2/credentials。
与 TypeScript CLI 共享同一凭据格式，确保互操作性。
"""

import getpass
import json
import os
import platform
import stat
from datetime import datetime
from pathlib import Path
from typing import Any

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

# 文件格式常量
MAGIC = b"HZ2C"
SALT_SIZE = 16
NONCE_SIZE = 12
PBKDF2_ITERATIONS = 100_000
KEY_SIZE = 32  # AES-256

# 默认路径
DEFAULT_CREDENTIALS_DIR = Path.home() / ".hezor2"
DEFAULT_CREDENTIALS_FILE = DEFAULT_CREDENTIALS_DIR / "credentials"


class ProfileData:
    """单个登录 Profile 的数据。"""

    def __init__(
        self,
        host: str,
        base_url: str,
        api_key: str,
        session_id: str,
        user_id: str,
        user_name: str,
        display_name: str,
        app_name: str | None = None,
        caller_id: str | None = None,
        logged_in_at: str | None = None,
        expires_at: str | None = None,
        client_id: str | None = None,
        client_secret: str | None = None,
        cert_content: str | None = None,
    ) -> None:
        self.host = host
        self.base_url = base_url
        self.api_key = api_key
        self.session_id = session_id
        self.user_id = user_id
        self.user_name = user_name
        self.display_name = display_name
        self.app_name = app_name
        self.caller_id = caller_id
        self.logged_in_at = logged_in_at or datetime.now().isoformat()
        self.expires_at = expires_at
        self.client_id = client_id
        self.client_secret = client_secret
        self.cert_content = cert_content

    def to_dict(self) -> dict[str, Any]:
        """序列化为字典。"""
        return {
            "host": self.host,
            "base_url": self.base_url,
            "api_key": self.api_key,
            "session_id": self.session_id,
            "user_id": self.user_id,
            "user_name": self.user_name,
            "display_name": self.display_name,
            "app_name": self.app_name,
            "caller_id": self.caller_id,
            "logged_in_at": self.logged_in_at,
            "expires_at": self.expires_at,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "cert_content": self.cert_content,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "ProfileData":
        """从字典反序列化。"""
        return cls(
            host=data["host"],
            base_url=data["base_url"],
            api_key=data["api_key"],
            session_id=data["session_id"],
            user_id=data["user_id"],
            user_name=data["user_name"],
            display_name=data["display_name"],
            app_name=data.get("app_name"),
            caller_id=data.get("caller_id"),
            logged_in_at=data.get("logged_in_at"),
            expires_at=data.get("expires_at"),
            client_id=data.get("client_id"),
            client_secret=data.get("client_secret"),
            cert_content=data.get("cert_content"),
        )


class CredentialManager:
    """凭据管理器。

    使用 AES-256-GCM 加密存储凭据，密钥由机器指纹推导。
    文件格式: [4B magic][16B salt][12B nonce][ciphertext][16B auth tag]
    """

    def __init__(self, credentials_path: Path | None = None) -> None:
        self._path = credentials_path or DEFAULT_CREDENTIALS_FILE

    def _derive_key(self, salt: bytes) -> bytes:
        """从机器指纹推导加密密钥。

        Parameters
        ----------
        salt : bytes
            随机 salt

        Returns
        -------
        bytes
            32 字节 AES-256 密钥
        """
        # 机器指纹: hostname + OS username + 固定字符串
        fingerprint = f"{platform.node()}:{getpass.getuser()}:hezor2-cli"
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=KEY_SIZE,
            salt=salt,
            iterations=PBKDF2_ITERATIONS,
        )
        return kdf.derive(fingerprint.encode("utf-8"))

    def _encrypt(self, plaintext: bytes) -> bytes:
        """AES-256-GCM 加密。

        Returns
        -------
        bytes
            格式: [4B magic][16B salt][12B nonce][ciphertext + 16B tag]
        """
        salt = os.urandom(SALT_SIZE)
        nonce = os.urandom(NONCE_SIZE)
        key = self._derive_key(salt)
        aesgcm = AESGCM(key)
        ciphertext = aesgcm.encrypt(nonce, plaintext, None)
        return MAGIC + salt + nonce + ciphertext

    def _decrypt(self, data: bytes) -> bytes:
        """AES-256-GCM 解密。

        Parameters
        ----------
        data : bytes
            加密后的完整数据（含 magic + salt + nonce + ciphertext）

        Returns
        -------
        bytes
            明文
        """
        if len(data) < len(MAGIC) + SALT_SIZE + NONCE_SIZE + 16:
            raise ValueError("Invalid credentials file: too short")

        if data[:4] != MAGIC:
            raise ValueError("Invalid credentials file: bad magic bytes")

        offset = len(MAGIC)
        salt = data[offset : offset + SALT_SIZE]
        offset += SALT_SIZE
        nonce = data[offset : offset + NONCE_SIZE]
        offset += NONCE_SIZE
        ciphertext = data[offset:]

        key = self._derive_key(salt)
        aesgcm = AESGCM(key)
        return aesgcm.decrypt(nonce, ciphertext, None)

    def _read_store(self) -> dict[str, Any]:
        """读取并解密凭据存储。"""
        if not self._path.exists():
            return {"version": 1, "profiles": {}, "active_profile": "default"}

        encrypted = self._path.read_bytes()
        plaintext = self._decrypt(encrypted)
        return json.loads(plaintext.decode("utf-8"))

    def _write_store(self, store: dict[str, Any]) -> None:
        """加密并写入凭据存储。"""
        self._path.parent.mkdir(parents=True, exist_ok=True)
        plaintext = json.dumps(store, ensure_ascii=False, indent=2).encode("utf-8")
        encrypted = self._encrypt(plaintext)
        self._path.write_bytes(encrypted)
        # 设置文件权限为 600（仅所有者可读写）
        self._path.chmod(stat.S_IRUSR | stat.S_IWUSR)

    def save_profile(self, profile_name: str, data: ProfileData) -> None:
        """保存 Profile。

        Parameters
        ----------
        profile_name : str
            Profile 名称
        data : ProfileData
            Profile 数据
        """
        store = self._read_store()
        store["profiles"][profile_name] = data.to_dict()
        store["active_profile"] = profile_name
        self._write_store(store)

    def load_profile(self, profile_name: str | None = None) -> ProfileData | None:
        """加载 Profile。

        Parameters
        ----------
        profile_name : str | None
            Profile 名称，默认使用 active_profile

        Returns
        -------
        ProfileData | None
            Profile 数据，不存在时返回 None
        """
        store = self._read_store()
        name = profile_name or store.get("active_profile", "default")
        profile_dict = store.get("profiles", {}).get(name)
        if not profile_dict:
            return None
        return ProfileData.from_dict(profile_dict)

    def delete_profile(self, profile_name: str) -> bool:
        """删除 Profile。

        Returns
        -------
        bool
            是否成功删除
        """
        store = self._read_store()
        if profile_name in store.get("profiles", {}):
            del store["profiles"][profile_name]
            if store.get("active_profile") == profile_name:
                remaining = list(store["profiles"].keys())
                store["active_profile"] = remaining[0] if remaining else "default"
            self._write_store(store)
            return True
        return False

    def list_profiles(self) -> list[str]:
        """列出所有 Profile 名称。"""
        store = self._read_store()
        return list(store.get("profiles", {}).keys())

    def get_active_profile_name(self) -> str:
        """获取当前活跃 Profile 名称。"""
        store = self._read_store()
        return store.get("active_profile", "default")

    def has_credentials(self) -> bool:
        """检查是否有已存储的凭据。"""
        return self._path.exists()
