"""配置管理模块：加载、合并、校验 TOML 配置文件与环境变量。"""

from __future__ import annotations

import os
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

if sys.version_info >= (3, 11):
    import tomllib
else:
    try:
        import tomllib
    except ModuleNotFoundError:  # pragma: no cover
        import tomli as tomllib  # type: ignore[no-redef]

import tomli_w


# ---------------------------------------------------------------------------
# 常量
# ---------------------------------------------------------------------------

GLOBAL_CONFIG_DIR = Path.home() / ".acm"
GLOBAL_CONFIG_FILE = GLOBAL_CONFIG_DIR / "config.toml"
PROJECT_CONFIG_FILE = ".acm.toml"

DEFAULT_COMMIT_TYPES: list[str] = [
    "feat", "fix", "docs", "style", "refactor", "perf", "test", "chore",
]

PROVIDER_DEFAULTS: dict[str, dict[str, str]] = {
    "qwen": {
        "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "env_key": "DASHSCOPE_API_KEY",
    },
}


# ---------------------------------------------------------------------------
# 数据类
# ---------------------------------------------------------------------------

@dataclass
class LLMConfig:
    provider: str = ""
    api_key: str = ""
    model: str = ""
    base_url: str = ""


@dataclass
class CommitConfig:
    language: str = "zh-CN"
    max_diff_lines: int = 500
    max_diff_chars: int = 15000
    types: list[str] = field(default_factory=list)


@dataclass
class PromptConfig:
    system: str = ""
    extra: str = ""


@dataclass
class ACMConfig:
    llm: LLMConfig = field(default_factory=LLMConfig)
    commit: CommitConfig = field(default_factory=CommitConfig)
    prompt: PromptConfig = field(default_factory=PromptConfig)


# ---------------------------------------------------------------------------
# 加载与合并
# ---------------------------------------------------------------------------

def _load_toml(path: Path) -> dict[str, Any]:
    """从文件加载 TOML，文件不存在时返回空 dict。"""
    if not path.is_file():
        return {}
    with open(path, "rb") as f:
        return tomllib.load(f)


def _deep_merge(base: dict, override: dict) -> dict:
    """深度合并两个 dict，override 覆盖 base。"""
    merged = base.copy()
    for key, val in override.items():
        if key in merged and isinstance(merged[key], dict) and isinstance(val, dict):
            merged[key] = _deep_merge(merged[key], val)
        else:
            merged[key] = val
    return merged


def _dict_to_config(data: dict[str, Any]) -> ACMConfig:
    """将 dict 转换为 ACMConfig 数据类。"""
    llm_data = data.get("llm", {})
    commit_data = data.get("commit", {})
    prompt_data = data.get("prompt", {})

    return ACMConfig(
        llm=LLMConfig(
            provider=llm_data.get("provider", ""),
            api_key=llm_data.get("api_key", ""),
            model=llm_data.get("model", ""),
            base_url=llm_data.get("base_url", ""),
        ),
        commit=CommitConfig(
            language=commit_data.get("language", "zh-CN"),
            max_diff_lines=commit_data.get("max_diff_lines", 500),
            max_diff_chars=commit_data.get("max_diff_chars", 15000),
            types=commit_data.get("types", []),
        ),
        prompt=PromptConfig(
            system=prompt_data.get("system", ""),
            extra=prompt_data.get("extra", ""),
        ),
    )


def _resolve_api_key(config: ACMConfig) -> str:
    """按优先级解析 API Key：环境变量 > 提供商专用环境变量 > 配置文件。"""
    # 1. 通用环境变量
    env_key = os.environ.get("ACM_API_KEY", "")
    if env_key:
        return env_key

    # 2. 提供商专用环境变量
    provider = config.llm.provider.lower()
    if provider in PROVIDER_DEFAULTS:
        provider_env = PROVIDER_DEFAULTS[provider].get("env_key", "")
        if provider_env:
            val = os.environ.get(provider_env, "")
            if val:
                return val

    # 3. 配置文件
    return config.llm.api_key


def _resolve_base_url(config: ACMConfig) -> str:
    """解析 base_url：配置文件值优先，否则使用提供商默认值。"""
    if config.llm.base_url:
        return config.llm.base_url
    provider = config.llm.provider.lower()
    if provider in PROVIDER_DEFAULTS:
        return PROVIDER_DEFAULTS[provider].get("base_url", "")
    return ""


def load_config(project_dir: str | Path | None = None) -> ACMConfig:
    """加载并合并配置，返回最终的 ACMConfig。

    优先级（高 → 低）：项目级配置 > 全局配置 > 默认值。
    API Key 额外叠加环境变量优先级。
    """
    global_data = _load_toml(GLOBAL_CONFIG_FILE)

    if project_dir is None:
        project_dir = Path.cwd()
    else:
        project_dir = Path(project_dir)
    project_path = project_dir / PROJECT_CONFIG_FILE
    project_data = _load_toml(project_path)

    merged = _deep_merge(global_data, project_data)
    config = _dict_to_config(merged)

    # 解析 API Key 和 base_url
    config.llm.api_key = _resolve_api_key(config)
    config.llm.base_url = _resolve_base_url(config)

    return config


# ---------------------------------------------------------------------------
# 校验
# ---------------------------------------------------------------------------

class ConfigError(Exception):
    """配置错误。"""


def validate_config(config: ACMConfig) -> None:
    """校验必填配置项，缺失时抛出 ConfigError。"""
    if not config.llm.api_key:
        raise ConfigError("未配置 API Key，请运行 acm init 进行初始化")
    if not config.llm.model:
        raise ConfigError("未配置模型名称，请运行 acm init 或编辑配置文件")


# ---------------------------------------------------------------------------
# 获取所有 commit types
# ---------------------------------------------------------------------------

def get_commit_types(config: ACMConfig) -> list[str]:
    """返回标准类型 + 用户自定义类型（去重）。"""
    all_types = list(DEFAULT_COMMIT_TYPES)
    for t in config.commit.types:
        if t not in all_types:
            all_types.append(t)
    return all_types


# ---------------------------------------------------------------------------
# 写入配置文件
# ---------------------------------------------------------------------------

def save_config(config: ACMConfig, path: Path) -> None:
    """将配置写入 TOML 文件。"""
    data: dict[str, Any] = {
        "llm": {
            "provider": config.llm.provider,
            "api_key": config.llm.api_key,
            "model": config.llm.model,
            "base_url": config.llm.base_url,
        },
        "commit": {
            "language": config.commit.language,
            "max_diff_lines": config.commit.max_diff_lines,
            "max_diff_chars": config.commit.max_diff_chars,
            "types": config.commit.types,
        },
        "prompt": {
            "system": config.prompt.system,
            "extra": config.prompt.extra,
        },
    }
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "wb") as f:
        tomli_w.dump(data, f)


def config_set(key: str, value: str, path: Path) -> None:
    """设置单个配置项，key 格式为 'section.field'，例如 'llm.model'。"""
    data = _load_toml(path)
    parts = key.split(".", 1)
    if len(parts) != 2:
        raise ConfigError(f"无效的配置键: {key}，格式应为 section.field")
    section, field_name = parts

    if section not in data:
        data[section] = {}

    # 尝试将数值字符串转为 int
    try:
        value_typed: Any = int(value)
    except ValueError:
        value_typed = value

    data[section][field_name] = value_typed
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "wb") as f:
        tomli_w.dump(data, f)


def config_get(key: str, config: ACMConfig) -> str:
    """获取单个配置项的值。"""
    parts = key.split(".", 1)
    if len(parts) != 2:
        raise ConfigError(f"无效的配置键: {key}，格式应为 section.field")
    section, field_name = parts

    section_obj = getattr(config, section, None)
    if section_obj is None:
        raise ConfigError(f"未知的配置节: {section}")

    value = getattr(section_obj, field_name, None)
    if value is None:
        raise ConfigError(f"未知的配置项: {key}")

    return str(value)
