"""Windows-local credential storage for the YOHOHOHO image plugin."""

from __future__ import annotations

import base64
import ctypes
import json
import os
import tempfile
from ctypes import wintypes
from pathlib import Path
from typing import Any


CONFIG_PATH = Path(
    os.environ.get(
        "YOHO_IMAGE_LOCAL_CONFIG",
        Path.home() / ".codex" / "yohohoho-image" / "config.json",
    )
)
CRYPTPROTECT_UI_FORBIDDEN = 0x01


class CredentialError(RuntimeError):
    pass


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


def _input_blob(data: bytes) -> tuple[DataBlob, ctypes.Array[Any]]:
    buffer = ctypes.create_string_buffer(data)
    return DataBlob(len(data), ctypes.cast(buffer, ctypes.POINTER(ctypes.c_char))), buffer


def _crypt32() -> tuple[Any, Any]:
    if os.name != "nt":
        raise CredentialError("YOHOHOHO 生图插件目前只支持 Windows。")
    return ctypes.windll.crypt32, ctypes.windll.kernel32


def protect_secret(value: str) -> str:
    if not isinstance(value, str) or not value.strip():
        raise CredentialError("API Key 不能为空。")
    crypt32, kernel32 = _crypt32()
    input_blob, _buffer = _input_blob(value.strip().encode("utf-8"))
    output_blob = DataBlob()
    if not crypt32.CryptProtectData(
        ctypes.byref(input_blob),
        None,
        None,
        None,
        None,
        CRYPTPROTECT_UI_FORBIDDEN,
        ctypes.byref(output_blob),
    ):
        raise CredentialError(f"无法加密 API Key，Windows 错误码：{ctypes.get_last_error()}")
    try:
        protected = ctypes.string_at(output_blob.pbData, output_blob.cbData)
        return base64.b64encode(protected).decode("ascii")
    finally:
        kernel32.LocalFree(output_blob.pbData)


def unprotect_secret(value: str) -> str:
    try:
        protected = base64.b64decode(value, validate=True)
    except (ValueError, TypeError) as exc:
        raise CredentialError("本地 API Key 配置已损坏，请重新运行 configure.cmd。") from exc
    crypt32, kernel32 = _crypt32()
    input_blob, _buffer = _input_blob(protected)
    output_blob = DataBlob()
    if not crypt32.CryptUnprotectData(
        ctypes.byref(input_blob),
        None,
        None,
        None,
        None,
        CRYPTPROTECT_UI_FORBIDDEN,
        ctypes.byref(output_blob),
    ):
        raise CredentialError("无法读取本地 API Key，请用当前 Windows 用户重新运行 configure.cmd。")
    try:
        return ctypes.string_at(output_blob.pbData, output_blob.cbData).decode("utf-8")
    finally:
        kernel32.LocalFree(output_blob.pbData)


def read_document() -> dict[str, Any]:
    try:
        document = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
    except FileNotFoundError as exc:
        raise CredentialError("尚未配置 YOHOHOHO API Key，请运行 configure.cmd。") from exc
    except (OSError, json.JSONDecodeError) as exc:
        raise CredentialError("本地插件配置无法读取，请重新运行 configure.cmd。") from exc
    if not isinstance(document, dict):
        raise CredentialError("本地插件配置格式无效，请重新运行 configure.cmd。")
    return document


def load_api_key() -> str:
    test_value = os.environ.get("YOHO_IMAGE_API_KEY")
    if test_value:
        return test_value.strip()
    document = read_document()
    protected = document.get("api_key_dpapi")
    if not isinstance(protected, str) or not protected:
        raise CredentialError("本地插件没有可用的 API Key，请重新运行 configure.cmd。")
    api_key = unprotect_secret(protected).strip()
    if not api_key:
        raise CredentialError("本地 API Key 为空，请重新运行 configure.cmd。")
    return api_key


def save_api_key(api_key: str) -> Path:
    CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
    document = {"version": 1, "api_key_dpapi": protect_secret(api_key)}
    temporary: Path | None = None
    try:
        with tempfile.NamedTemporaryFile(
            "w",
            encoding="utf-8",
            dir=CONFIG_PATH.parent,
            prefix="config.",
            suffix=".tmp",
            delete=False,
        ) as handle:
            temporary = Path(handle.name)
            handle.write(json.dumps(document, ensure_ascii=False, indent=2) + "\n")
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(temporary, CONFIG_PATH)
    except OSError as exc:
        if temporary:
            try:
                temporary.unlink(missing_ok=True)
            except OSError:
                pass
        raise CredentialError(f"无法保存本地插件配置：{exc}") from exc
    return CONFIG_PATH
