"""首次 / 新 HOME 时的交互式 LLM 必要项配置。"""

from __future__ import annotations

import os
import sys
from pathlib import Path

import typer
import yaml

from aicd.config import (
    PROTO_DASHSCOPE,
    PROTO_OLLAMA,
    PROTO_OPENAI,
    VALID_PROTOS,
    AppConfig,
    effective_llm_config,
    save_config,
)

MARKER_NAME = "setup_wizard_completed"


def setup_marker_path(layout: dict[str, Path]) -> Path:
    return layout["data"] / MARKER_NAME


def is_setup_complete(layout: dict[str, Path]) -> bool:
    return setup_marker_path(layout).is_file()


def mark_setup_complete(layout: dict[str, Path]) -> None:
    layout["data"].mkdir(parents=True, exist_ok=True)
    setup_marker_path(layout).write_text("1\n", encoding="utf-8")


def should_prompt_setup(*, relaxed: bool = False) -> bool:
    """
    是否适合在**当前进程**里用 ``typer.prompt`` 做首次配置。

    - **relaxed=False**（默认，如 ``aicd init``）：要求 stdin 与 stdout 均为 TTY，
      避免管道/重定向时半套交互。
    - **relaxed=True**（``aicd tui`` 启动）：仅要求 **stdin** 为 TTY。
      在 Windows / Cursor 等环境下 stdout 常非 TTY，否则向导会被误跳过，用户只见「未配置密钥」。
    """
    v = os.environ.get("AICD_SKIP_SETUP", "").strip().lower()
    if v in ("1", "true", "yes"):
        return False
    try:
        if not sys.stdin.isatty():
            return False
        if relaxed:
            return True
        return bool(sys.stdout.isatty())
    except (AttributeError, ValueError):
        return False


def config_file_has_llm_settings(cfg_path: Path) -> bool:
    """
    判断 ``config.yaml`` 是否已包含用户级 LLM 配置（区别于 ``init_home`` 写入的默认空项）。
    仅读磁盘上的 YAML，不依赖环境变量。
    """
    if not cfg_path.is_file():
        return False
    try:
        with cfg_path.open("r", encoding="utf-8") as f:
            raw = yaml.safe_load(f) or {}
    except (OSError, UnicodeDecodeError, yaml.YAMLError):
        return False
    if not isinstance(raw, dict):
        return False
    llm = raw.get("llm")
    if not isinstance(llm, dict):
        return False

    def _non_empty(*keys: str) -> bool:
        for k in keys:
            v = llm.get(k)
            if v is not None and str(v).strip():
                return True
        return False

    if _non_empty("apiKey", "api_key", "key"):
        return True
    if _non_empty("baseUrl", "base_url", "url"):
        return True
    if _non_empty("visionModel", "vision_model"):
        return True
    proto = str(llm.get("proto") or llm.get("PROTO") or "").strip().lower()
    if proto and proto != PROTO_OPENAI:
        return True
    model = str(
        llm.get("modelId") or llm.get("model_id") or llm.get("model") or ""
    ).strip()
    if model and model != "gpt-4o-mini":
        return True
    return False


def runtime_llm_ready(cfg: AppConfig) -> bool:
    """合并环境变量密钥等之后，当前配置是否已具备调用模型所需条件。"""
    eff = effective_llm_config(cfg.llm)
    p = eff.proto.lower()
    if p == PROTO_OLLAMA:
        return True
    if p in (PROTO_OPENAI, PROTO_DASHSCOPE):
        return bool((eff.api_key or "").strip())
    return True


def skip_first_time_setup_interactive(
    layout: dict[str, Path],
    cfg_path: Path,
    cfg: AppConfig,
) -> bool:
    """
    是否跳过交互式首次配置：已有完成标记、HOME 下 config 已写 LLM 项、
    或环境与配置已能调用模型（例如仅依赖环境变量中的 API Key）。
    调用方须已对本 HOME 的 ``cfg`` 执行过 ``merge_env_api_key``。
    """
    if is_setup_complete(layout):
        return True
    if config_file_has_llm_settings(cfg_path):
        return True
    if runtime_llm_ready(cfg):
        return True
    return False


def run_first_time_setup(
    layout: dict[str, Path],
    cfg: AppConfig,
    *,
    home_label: str,
) -> AppConfig:
    """
    在终端中询问 LLM 必要项并写入 ``config.yaml``，最后写入完成标记。
    调用前应对 ``cfg`` 已执行过 ``merge_env_api_key``（可选）。
    """
    from aicd.agent.loop import merge_env_api_key

    typer.echo(typer.style("\n=== Aicd 首次配置 ===", fg=typer.colors.CYAN, bold=True))
    typer.echo(f"数据目录 HOME: {home_label}")
    typer.echo(
        "将写入 LLM 相关项到 config.yaml；之后可用 TUI 的 /llm 或手动编辑该文件修改。\n"
    )

    merge_env_api_key(cfg)

    proto_raw = typer.prompt(
        "协议 proto",
        default=(cfg.llm.proto or PROTO_OPENAI).strip().lower(),
        show_default=True,
    ).strip().lower()
    if proto_raw not in VALID_PROTOS:
        typer.secho(f"未知协议 {proto_raw!r}，使用 openai", fg=typer.colors.YELLOW)
        proto_raw = PROTO_OPENAI
    cfg.llm.proto = proto_raw

    if proto_raw == PROTO_OPENAI:
        base_hint = "留空则使用默认 https://api.openai.com/v1"
    elif proto_raw == PROTO_DASHSCOPE:
        base_hint = "留空则使用默认 DashScope 兼容地址"
    else:
        base_hint = "留空则使用默认 http://127.0.0.1:11434/v1"

    cur_base = (cfg.llm.base_url or "").strip()
    base_in = typer.prompt(
        f"API Base URL（{base_hint}）",
        default=cur_base,
        show_default=bool(cur_base),
    ).strip()
    cfg.llm.base_url = base_in

    has_key = bool((cfg.llm.api_key or "").strip())
    typer.echo(
        "API Key：输入新密钥；直接回车则保留当前值（含已从环境变量注入的密钥）。"
        + (" 当前已检测到密钥。" if has_key else "")
    )
    new_key = typer.prompt(
        "API Key",
        default="",
        show_default=False,
        hide_input=True,
    )
    if new_key.strip():
        cfg.llm.api_key = new_key.strip()

    merge_env_api_key(cfg)

    default_model = (cfg.llm.model or "gpt-4o-mini").strip() or "gpt-4o-mini"
    if proto_raw == PROTO_OLLAMA and default_model == "gpt-4o-mini":
        default_model = "llama3.2"
    model_in = typer.prompt(
        "主模型 modelId",
        default=default_model,
        show_default=True,
    ).strip()
    cfg.llm.model = model_in or default_model

    cur_vm = (cfg.llm.vision_model or "").strip()
    vm_in = typer.prompt(
        "视觉模型 visionModel（可选，回车跳过）",
        default=cur_vm,
        show_default=bool(cur_vm),
    ).strip()
    cfg.llm.vision_model = vm_in if vm_in else None

    save_config(layout["config"], cfg)
    mark_setup_complete(layout)

    eff = effective_llm_config(cfg.llm)
    proto = eff.proto.lower()
    if proto in (PROTO_OPENAI, PROTO_DASHSCOPE) and not (eff.api_key or "").strip():
        typer.secho(
            "\n警告: 仍未检测到 API Key，主对话将无法调用模型。"
            "请设置环境变量 OPENAI_API_KEY / DASHSCOPE_API_KEY，或编辑 config.yaml 中的 llm.apiKey。\n",
            fg=typer.colors.YELLOW,
        )
    else:
        typer.secho("\n配置已保存。可以开始对话。\n", fg=typer.colors.GREEN)

    return cfg
