"""
密码安全存储模块 — 单机版

使用 Windows DPAPI 加密存储用户密码，实现开机自动解锁 user.db。

安全模型：
- 密码加密存储在文件系统中（data/.password_store）
- 使用 Windows DPAPI（当前用户凭据加密）
- 只有同一 Windows 用户可以解密
- 换用户/换机器无法解密

生命周期：
- 首次运行：无密码存储，需要用户设置
- 设置密码：密码经 DPAPI 加密后存储
- 后续启动：自动解密密码，缓存到进程内存
- 修改密码：更新存储
- 清除密码：删除存储文件
"""
import os
import json
import logging
import platform
from pathlib import Path

logger = logging.getLogger(__name__)

# 存储文件路径（相对于数据目录）
_STORE_FILENAME = ".password_store"


def _get_store_path() -> str:
    """获取密码存储文件路径"""
    from . import paths
    data_dir = paths.get_data_dir()
    return os.path.join(data_dir, _STORE_FILENAME)


def _dpapi_encrypt(data: str) -> bytes:
    """
    使用 Windows DPAPI 加密数据
    返回加密后的 bytes
    """
    if platform.system() != "Windows":
        raise RuntimeError("DPAPI 仅支持 Windows 系统")

    import ctypes
    import ctypes.wintypes

    # DataBlob 结构体
    class DATA_BLOB(ctypes.Structure):
        _fields_ = [("cbData", ctypes.wintypes.DWORD),
                     ("pbData", ctypes.POINTER(ctypes.c_char))]

    # 加载 crypt32.dll
    crypt32 = ctypes.windll.crypt32
    kernel32 = ctypes.windll.kernel32

    # 准备输入数据
    data_bytes = data.encode("utf-8")
    input_blob = DATA_BLOB(len(data_bytes), ctypes.create_string_buffer(data_bytes, len(data_bytes)))
    output_blob = DATA_BLOB()

    # CryptProtectData
    CRYPTPROTECT_UI_FORBIDDEN = 0x01
    if not crypt32.CryptProtectData(
        ctypes.byref(input_blob),
        None,  # description
        None,  # entropy
        None,  # reserved
        None,  # prompt struct
        CRYPTPROTECT_UI_FORBIDDEN,
        ctypes.byref(output_blob)
    ):
        error = ctypes.GetLastError()
        raise RuntimeError(f"DPAPI 加密失败: 错误码 {error}")

    # 读取加密数据
    encrypted = ctypes.string_at(output_blob.pbData, output_blob.cbData)
    kernel32.LocalFree(output_blob.pbData)

    return encrypted


def _dpapi_decrypt(encrypted: bytes) -> str:
    """
    使用 Windows DPAPI 解密数据
    """
    if platform.system() != "Windows":
        raise RuntimeError("DPAPI 仅支持 Windows 系统")

    import ctypes
    import ctypes.wintypes

    class DATA_BLOB(ctypes.Structure):
        _fields_ = [("cbData", ctypes.wintypes.DWORD),
                     ("pbData", ctypes.POINTER(ctypes.c_char))]

    crypt32 = ctypes.windll.crypt32
    kernel32 = ctypes.windll.kernel32

    input_blob = DATA_BLOB(len(encrypted), ctypes.create_string_buffer(encrypted, len(encrypted)))
    output_blob = DATA_BLOB()

    CRYPTPROTECT_UI_FORBIDDEN = 0x01
    if not crypt32.CryptUnprotectData(
        ctypes.byref(input_blob),
        None,  # description
        None,  # entropy
        None,  # reserved
        None,  # prompt struct
        CRYPTPROTECT_UI_FORBIDDEN,
        ctypes.byref(output_blob)
    ):
        error = ctypes.GetLastError()
        raise RuntimeError(f"DPAPI 解密失败: 错误码 {error}")

    decrypted = ctypes.string_at(output_blob.pbData, output_blob.cbData)
    kernel32.LocalFree(output_blob.pbData)

    return decrypted.decode("utf-8")


def has_stored_password() -> bool:
    """检查是否有存储的密码"""
    store_path = _get_store_path()
    return os.path.exists(store_path)


def save_password(password: str):
    """
    保存密码到安全存储（DPAPI 加密）
    """
    store_path = _get_store_path()

    # 确保目录存在
    os.makedirs(os.path.dirname(store_path) or ".", exist_ok=True)

    # 加密密码
    encrypted = _dpapi_encrypt(password)

    # 写入文件（二进制）
    with open(store_path, "wb") as f:
        f.write(encrypted)

    # 设置文件权限（仅当前用户可读写）
    try:
        os.chmod(store_path, 0o600)
    except Exception:
        pass

    logger.info("密码已安全存储")


def load_password() -> str | None:
    """
    从安全存储加载密码
    返回 None 表示无存储或解密失败
    """
    store_path = _get_store_path()
    if not os.path.exists(store_path):
        return None

    try:
        with open(store_path, "rb") as f:
            encrypted = f.read()
        password = _dpapi_decrypt(encrypted)
        return password
    except Exception as e:
        logger.warning(f"密码解密失败（可能是换了用户或机器）: {e}")
        return None


def clear_password():
    """清除存储的密码"""
    store_path = _get_store_path()
    if os.path.exists(store_path):
        # 先覆写再删除（安全擦除）
        size = os.path.getsize(store_path)
        with open(store_path, "wb") as f:
            f.write(b"\x00" * size)
        os.remove(store_path)
        logger.info("已清除存储的密码")


def update_password(old_password: str, new_password: str) -> bool:
    """
    更新密码：验证旧密码后保存新密码
    返回 True 表示成功
    """
    # 验证旧密码
    stored = load_password()
    if stored and stored != old_password:
        return False

    # 保存新密码
    save_password(new_password)
    return True


# ============ 非 Windows 平台的文件加密兜底 ============

def _fallback_encrypt(data: str) -> bytes:
    """
    非 Windows 平台的文件加密兜底（基于固定密钥 XOR）
    安全性低于 DPAPI，但优于明文存储
    """
    from .crypto import APP_DEFAULT_KEY
    key = APP_DEFAULT_KEY.encode("utf-8")
    data_bytes = data.encode("utf-8")
    # 简单 XOR 加密（非安全场景兜底）
    encrypted = bytes(b ^ key[i % len(key)] for i, b in enumerate(data_bytes))
    return encrypted


def _fallback_decrypt(encrypted: bytes) -> str:
    """非 Windows 平台的文件解密兜底"""
    from .crypto import APP_DEFAULT_KEY
    key = APP_DEFAULT_KEY.encode("utf-8")
    decrypted = bytes(b ^ key[i % len(key)] for i, b in enumerate(encrypted))
    return decrypted.decode("utf-8")


def save_password_cross_platform(password: str):
    """跨平台密码保存（Windows 用 DPAPI，其他用文件加密）"""
    store_path = _get_store_path()
    os.makedirs(os.path.dirname(store_path) or ".", exist_ok=True)

    if platform.system() == "Windows":
        encrypted = _dpapi_encrypt(password)
    else:
        encrypted = _fallback_encrypt(password)

    with open(store_path, "wb") as f:
        f.write(encrypted)

    try:
        os.chmod(store_path, 0o600)
    except Exception:
        pass

    logger.info("密码已安全存储")


def load_password_cross_platform() -> str | None:
    """跨平台密码加载"""
    store_path = _get_store_path()
    if not os.path.exists(store_path):
        return None

    try:
        with open(store_path, "rb") as f:
            encrypted = f.read()

        if platform.system() == "Windows":
            return _dpapi_decrypt(encrypted)
        else:
            return _fallback_decrypt(encrypted)
    except Exception as e:
        logger.warning(f"密码解密失败: {e}")
        return None
