from __future__ import annotations

import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from .exceptions import GrsAIError
from .models import ClientConfig, DrawConfig, PollConfig

DEFAULT_CONFIG_NAME = "nbdraw.toml"
LEGACY_CONFIG_NAME = "grsai_draw.toml"
DEFAULT_PROMPT_NAME = "prompt.md"


def read_text_file(path: str) -> str:
    with open(path, "r", encoding="utf-8") as handle:
        return handle.read().strip()


def parse_kv_list(items: Optional[List[str]]) -> Dict[str, Any]:
    out: Dict[str, Any] = {}
    if not items:
        return out

    def coerce(value: str) -> Any:
        lowered = value.lower()
        if lowered == "true":
            return True
        if lowered == "false":
            return False

        if value.isdigit() or (value.startswith("-") and value[1:].isdigit()):
            return int(value)

        try:
            if any(c in value for c in (".", "e", "E")):
                return float(value)
        except ValueError:
            pass
        return value

    for item in items:
        if "=" not in item:
            raise GrsAIError(f"--set 参数格式错误：{item!r}，应为 key=value")
        key, value = item.split("=", 1)
        key = key.strip()
        value = value.strip()
        if not key:
            raise GrsAIError(f"--set 参数 key 为空：{item!r}")
        out[key] = coerce(value)

    return out


def load_config_file(path: str) -> Dict[str, Any]:
    if not os.path.exists(path):
        raise GrsAIError(f"配置文件不存在：{path}")

    ext = os.path.splitext(path)[1].lower()
    if ext == ".json":
        with open(path, "r", encoding="utf-8") as handle:
            return json.load(handle)

    if ext == ".toml":
        try:
            import tomllib  # type: ignore[attr-defined]

            loader = tomllib
        except ModuleNotFoundError:
            try:
                import tomli as loader
            except ModuleNotFoundError as exc:
                raise GrsAIError("无法读取 TOML：请使用 Python 3.11+ 或安装 tomli") from exc

        with open(path, "rb") as handle:
            return loader.load(handle)

    raise GrsAIError("仅支持 .toml 或 .json 配置文件")


def merge_dict(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]:
    out = dict(a or {})
    for key, value in (b or {}).items():
        if isinstance(out.get(key), dict) and isinstance(value, dict):
            out[key] = merge_dict(out[key], value)
        else:
            out[key] = value
    return out


def resolve_config_path(
    explicit_config: Optional[str], cwd: Optional[Path] = None
) -> Optional[str]:
    current = cwd or Path.cwd()
    if explicit_config:
        explicit_path = Path(explicit_config)
        if explicit_path.exists():
            return str(explicit_path)
        raise GrsAIError(f"配置文件不存在：{explicit_config}")

    for name in (DEFAULT_CONFIG_NAME, LEGACY_CONFIG_NAME):
        candidate = current / name
        if candidate.exists():
            return str(candidate)

    return None


def build_configs(args: Any) -> Tuple[ClientConfig, DrawConfig, PollConfig]:
    raw: Dict[str, Any] = {"client": {}, "draw": {}, "poll": {}}

    config_path = resolve_config_path(getattr(args, "config", None))
    if config_path:
        raw = merge_dict(raw, load_config_file(config_path))

    if os.getenv("GRSAI_BASE_URL"):
        raw["client"]["base_url"] = os.getenv("GRSAI_BASE_URL")
    if os.getenv("GRSAI_API_KEY"):
        raw["client"]["api_key"] = os.getenv("GRSAI_API_KEY")

    if args.base_url:
        raw["client"]["base_url"] = args.base_url
    if args.api_key:
        raw["client"]["api_key"] = args.api_key

    for key in ["timeout", "connect_timeout", "retries", "backoff_base", "backoff_cap"]:
        value = getattr(args, key, None)
        if value is not None:
            raw["client"][key] = value

    if args.model:
        raw["draw"]["model"] = args.model
    if args.aspect_ratio:
        raw["draw"]["aspect_ratio"] = args.aspect_ratio
    if args.image_size:
        raw["draw"]["image_size"] = args.image_size
    if args.web_hook:
        raw["draw"]["web_hook"] = args.web_hook

    sets = parse_kv_list(args.set)
    if sets:
        draw_raw = raw.get("draw", {})
        extra = draw_raw.get("extra", {}) if isinstance(draw_raw.get("extra"), dict) else {}
        known = {"model", "aspect_ratio", "image_size", "web_hook"}
        for key, value in sets.items():
            if key in known:
                draw_raw[key] = value
            else:
                extra[key] = value
        draw_raw["extra"] = extra
        raw["draw"] = draw_raw

    if args.poll_interval is not None:
        raw["poll"]["interval"] = args.poll_interval
    if args.max_wait is not None:
        raw["poll"]["max_wait"] = args.max_wait
    if args.heartbeat is not None:
        raw["poll"]["heartbeat"] = args.heartbeat

    client_raw = raw.get("client", {})
    api_key = (client_raw.get("api_key") or "").strip()
    if not api_key:
        raise GrsAIError("缺少 API Key：请在配置文件、环境变量 GRSAI_API_KEY 或 --api-key 提供")

    client_cfg = ClientConfig(
        api_key=api_key,
        base_url=str(client_raw.get("base_url", "https://grsai.dakka.com.cn")),
        timeout=float(client_raw.get("timeout", 60.0)),
        connect_timeout=float(client_raw.get("connect_timeout", 5.0)),
        retries=int(client_raw.get("retries", 3)),
        backoff_base=float(client_raw.get("backoff_base", 1.0)),
        backoff_cap=float(client_raw.get("backoff_cap", 8.0)),
    )

    draw_raw = raw.get("draw", {})
    draw_cfg = DrawConfig(
        model=str(draw_raw.get("model", "nano-banana-fast")),
        aspect_ratio=str(draw_raw.get("aspect_ratio", "16:9")),
        image_size=str(draw_raw.get("image_size", "1K")),
        web_hook=str(draw_raw.get("web_hook", "-1")),
        extra=dict(draw_raw.get("extra", {}) or {}),
    )

    poll_raw = raw.get("poll", {})
    poll_cfg = PollConfig(
        interval=float(poll_raw.get("interval", 5.0)),
        max_wait=float(poll_raw.get("max_wait", 300.0)),
        heartbeat=float(poll_raw.get("heartbeat", 15.0)),
    )

    return client_cfg, draw_cfg, poll_cfg


def resolve_prompt(args: Any) -> str:
    if args.stdin:
        return (sys.stdin.read() or "").strip()
    if args.prompt_file:
        return read_text_file(args.prompt_file)
    if args.prompt:
        return args.prompt.strip()

    default_prompt = Path.cwd() / DEFAULT_PROMPT_NAME
    if default_prompt.exists():
        return read_text_file(str(default_prompt))

    raise GrsAIError(
        "未提供提示词：请使用 --prompt / --prompt-file / --stdin，或在当前目录放置 prompt.md"
    )
