# -*- coding: utf-8 -*-
"""abyss 原生配置管理（向后兼容模块）。

原 `src/abyss/config.py` 的内容，迁移到 `abyss.config` 包下保留为 `legacy` 子模块。
所有 `from abyss.config import Config, init_user_dirs, CONFIG_DIR, CONFIG_FILE`
等导入保持向后兼容。
"""
import json
from pathlib import Path

CONFIG_DIR = Path.home() / ".abyss"
CONFIG_FILE = CONFIG_DIR / "config.json"


class Config:
    """管理用户配置，存储在 ~/.abyss/config.json。

    使用原子写入防止配置文件损坏导致数据丢失：
    - save() 先写临时文件，成功后再替换正式文件
    - _load() 遇到损坏文件时，自动创建 .bak 备份后回退默认值
    """

    DEFAULTS = {
        "api_key": "",
        "base_url": "https://api.deepseek.com",
        "model": "deepseek-v4-pro",
        "thinking": True,
        "reasoning_effort": "high",
        "auto_confirm": False,
        "max_tokens": 65536,  # 默认 64K；deepseek-v4 输出上限 384K，可按需调大
        "show_reasoning": False,
        "user_id": "default",
        "max_retries": 3,
        "retry_delay": 1.0,
        "auto_save_interval": 30,
        "tool_result_max_chars": 4000,
        # 流式响应相邻 chunk 间隔上限（秒）：超过则抛 TimeoutError，
        # 避免服务端挂起时主进程无限阻塞（用户报告过模型卡在 reasoning_start 不再产出）
        "stream_idle_timeout": 120,
        # 工具失败自纠错循环最大重试次数（SelfCorrectionLoop）
        "self_correction_max_attempts": 3,
        # 自适应思考预算：启用后根据上一轮的 tool_calls/output_tokens/turn_count
        # 三因子动态计算 reasoning_effort，简单问答省 token，复杂调试增强推理
        "adaptive_thinking_budget": False,
        # UI 主题：'hacker' 复古 CRT 风格（默认）/ 'default' 保持传统终端
        "ui.theme": "hacker",
        # hacker theme 专用：CRT 扫描线效果（每行深浅交替背景，性能敏感时关闭）
        "ui.hacker.use_crt_scanline": False,
    }

    MODELS = {
        "deepseek-v4-pro": {"context": "1M", "output": "384K", "thinking": True},
        "deepseek-v4-flash": {"context": "1M", "output": "384K", "thinking": True},
    }

    def __init__(self):
        self._config = {}
        self._load()

    def _load(self):
        """从配置文件加载配置。文件损坏时备份后回退默认值；缺失字段自动补齐默认值。"""
        if not CONFIG_FILE.exists():
            self._config = dict(self.DEFAULTS)
            return

        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                loaded = json.load(f)
            if not isinstance(loaded, dict):
                self._backup_broken_file("非 dict 类型")
                self._config = dict(self.DEFAULTS)
            else:
                # 合并 DEFAULTS：文件里已有的值优先，缺失的字段补默认值
                self._config = {**self.DEFAULTS, **loaded}
        except json.JSONDecodeError:
            self._backup_broken_file("JSON 解析失败")
            self._config = dict(self.DEFAULTS)
        except (IOError, OSError):
            self._backup_broken_file("文件不可读")
            self._config = dict(self.DEFAULTS)

    def _backup_broken_file(self, reason: str):
        """将损坏的配置文件重命名为 .bak，避免被默认配置覆盖丢失数据。"""
        try:
            bak = CONFIG_FILE.with_suffix(".json.bak")
            CONFIG_FILE.replace(bak)
        except Exception:
            pass

    def save(self):
        """原子保存配置：写入临时文件后替换正式文件，防止写入中断导致文件损坏。"""
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        tmp_path = CONFIG_FILE.with_suffix(".tmp")
        try:
            with open(tmp_path, "w", encoding="utf-8") as f:
                json.dump(self._config, f, indent=2, ensure_ascii=False)
            tmp_path.replace(CONFIG_FILE)
        except Exception:
            # 清理临时文件
            try:
                tmp_path.unlink(missing_ok=True)
            except Exception:
                pass
            raise

    def get(self, key: str, default=None):
        """获取配置项，未设置时返回 default（优先级高于 DEFAULTS）"""
        if default is not None:
            return self._config.get(key, self.DEFAULTS.get(key, default))
        return self._config.get(key, self.DEFAULTS.get(key))

    def set(self, key: str, value):
        """设置配置项"""
        self._config[key] = value
        self.save()

    def delete(self, key: str) -> bool:
        """删除配置项（用于 /config unset 清理垃圾数据）。

        幂等：key 不存在时不抛异常。
        返回 True 表示删除了 key，False 表示 key 本来就不在。
        """
        if key in self._config:
            del self._config[key]
            self.save()
            return True
        return False

    def is_ready(self) -> bool:
        """检查是否已配置 API Key"""
        return bool(self.get("api_key"))

    def to_dict(self) -> dict:
        """返回配置字典"""
        return dict(self._config)

    def get_model_info(self) -> dict:
        """获取当前模型信息"""
        model = self.get("model")
        return self.MODELS.get(model, {})


# ---- 子目录骨架 ----
_SKEL_DIRS = ["skills", "hooks", "commands", "logs", "knowledge", "sessions"]
SESSION_DIR = CONFIG_DIR / "sessions"
_HOOK_EVENT_DIRS = ["PreToolUse", "PostToolUse"]


def init_user_dirs(base_dir: Path = None) -> None:
    """首次启动时创建 ~/.abyss/ 骨架目录和 mcp.json 模板。
    幂等：目录已存在不报错，mcp.json 已存在不覆盖。
    """
    base = base_dir or CONFIG_DIR
    base.mkdir(parents=True, exist_ok=True)
    for sub in _SKEL_DIRS:
        (base / sub).mkdir(exist_ok=True)
    for ev in _HOOK_EVENT_DIRS:
        (base / "hooks" / ev).mkdir(exist_ok=True)
    mcp_file = base / "mcp.json"
    if not mcp_file.exists():
        mcp_file.write_text(
            json.dumps({"mcp_servers": []}, indent=2, ensure_ascii=False),
            encoding="utf-8",
        )
