"""Config file management for agentkit.

Supports two config systems:
1. Legacy .agentkit.yaml (kept for backward compat)
2. New .agentkit.toml / ~/.config/agentkit/config.toml (v0.22.0+)

Precedence: CLI flags > env vars > project .agentkit.toml > user config.toml > built-in defaults
"""
from __future__ import annotations

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional

try:
    import tomllib  # Python 3.11+
except ImportError:
    try:
        import tomli as tomllib  # type: ignore
    except ImportError:
        tomllib = None  # type: ignore

try:
    import yaml  # type: ignore
    HAS_YAML = True
except ImportError:
    HAS_YAML = False


# ---------------------------------------------------------------------------
# Legacy YAML config (kept for backward compat)
# ---------------------------------------------------------------------------

CONFIG_FILENAME = ".agentkit.yaml"
LAST_RUN_FILENAME = ".agentkit-last-run.json"

DEFAULT_CONFIG = {
    "tools": {
        "coderace": True,
        "agentmd": True,
        "agentlint": True,
        "agentreflect": True,
    },
    "defaults": {
        "min_score": 80,
        "context_file": "CLAUDE.md",
    },
}

DEFAULT_CONFIG_YAML = """\
tools:
  coderace: true
  agentmd: true
  agentlint: true
  agentreflect: true
defaults:
  min_score: 80
  context_file: CLAUDE.md
"""


def find_project_root(start: Optional[Path] = None) -> Path:
    """Walk up to find git root; fall back to cwd."""
    cwd = start or Path.cwd()
    current = cwd
    while True:
        if (current / ".git").exists():
            return current
        parent = current.parent
        if parent == current:
            break
        current = parent
    return cwd


def config_path(root: Optional[Path] = None) -> Path:
    root = root or find_project_root()
    return root / CONFIG_FILENAME


def last_run_path(root: Optional[Path] = None) -> Path:
    root = root or find_project_root()
    return root / LAST_RUN_FILENAME


def config_exists(root: Optional[Path] = None) -> bool:
    return config_path(root).exists()


def write_default_config(root: Optional[Path] = None) -> Path:
    path = config_path(root)
    path.write_text(DEFAULT_CONFIG_YAML)
    return path


def load_last_run(root: Optional[Path] = None) -> Optional[dict]:
    import json
    p = last_run_path(root)
    if not p.exists():
        return None
    try:
        return json.loads(p.read_text())
    except Exception:
        return None


def save_last_run(data: dict, root: Optional[Path] = None) -> None:
    import json
    p = last_run_path(root)
    p.write_text(json.dumps(data, indent=2))


# ---------------------------------------------------------------------------
# New TOML-based config system (v0.22.0)
# ---------------------------------------------------------------------------

TOML_FILENAME = ".agentkit.toml"
USER_CONFIG_DIR = Path.home() / ".config" / "agentkit"
USER_CONFIG_FILE = USER_CONFIG_DIR / "config.toml"

DEFAULT_TOML_TEMPLATE = """\
# agentkit project configuration
# Generated by: agentkit config init
# See: https://github.com/mikiships/agentkit-cli#project-configuration

[gate]
# Minimum composite score to pass the gate (0-100)
min_score = 75
# Maximum allowed score drop from baseline
max_drop = 10.0
# Fail if any tool regresses even within max_drop
fail_on_regression = false

[notify]
# Slack incoming webhook URL (or set AGENTKIT_SLACK_URL)
slack_url = ""
# Discord webhook URL (or set AGENTKIT_DISCORD_URL)
discord_url = ""
# Generic JSON webhook URL (or set AGENTKIT_WEBHOOK_URL)
webhook_url = ""
# When to send notifications: "always" | "fail" | "never"
on = "fail"

[run]
# Directory to store agentkit output artifacts
output_dir = ".agentkit"
# Optional label/tag for this project's runs (e.g. model name)
label = ""
# Record scores to local history DB
record_history = true

[sweep]
# Default targets for agentkit sweep (list of repo paths/URLs)
targets = []
# Sort results by: score, name, grade
sort_by = "score"
# Maximum number of results to display
limit = 20

[score]
# Weights for composite score calculation (must sum to 1.0)
[score.weights]
coderace = 0.3
agentlint = 0.3
agentmd = 0.2
agentreflect = 0.2
"""


@dataclass
class GateConfig:
    min_score: Optional[float] = None
    max_drop: Optional[float] = None
    fail_on_regression: bool = False


@dataclass
class NotifyConfig:
    slack_url: str = ""
    discord_url: str = ""
    webhook_url: str = ""
    on: str = "fail"


@dataclass
class RunConfig:
    output_dir: str = ".agentkit"
    label: str = ""
    record_history: bool = True


@dataclass
class SweepConfig:
    targets: List[str] = field(default_factory=list)
    sort_by: str = "score"
    limit: int = 20


@dataclass
class ScoreWeights:
    coderace: float = 0.3
    agentlint: float = 0.3
    agentmd: float = 0.2
    agentreflect: float = 0.2


@dataclass
class ScoreConfig:
    weights: ScoreWeights = field(default_factory=ScoreWeights)


@dataclass
class AgentKitConfig:
    """Resolved agentkit configuration from all sources."""
    gate: GateConfig = field(default_factory=GateConfig)
    notify: NotifyConfig = field(default_factory=NotifyConfig)
    run: RunConfig = field(default_factory=RunConfig)
    sweep: SweepConfig = field(default_factory=SweepConfig)
    score: ScoreConfig = field(default_factory=ScoreConfig)

    # Source tracking for show command
    _sources: Dict[str, str] = field(default_factory=dict, repr=False)


def _find_toml_config(start: Optional[Path] = None) -> Optional[Path]:
    """Walk up from start dir to find .agentkit.toml (git-style)."""
    cwd = start or Path.cwd()
    current = cwd
    while True:
        candidate = current / TOML_FILENAME
        if candidate.exists():
            return candidate
        parent = current.parent
        if parent == current:
            break
        current = parent
    return None


def _parse_toml(path: Path) -> Dict[str, Any]:
    """Parse a TOML file, returning {} on error."""
    if tomllib is None:
        return {}
    try:
        with open(path, "rb") as f:
            return tomllib.load(f)
    except Exception as e:
        import sys
        print(f"Warning: failed to parse {path}: {e}", file=sys.stderr)
        return {}


def _merge_gate(data: Dict[str, Any], config: GateConfig, source: str, sources: Dict[str, str]) -> None:
    g = data.get("gate", {})
    if "min_score" in g and g["min_score"] != "":
        config.gate.min_score = float(g["min_score"])
        sources["gate.min_score"] = source
    if "max_drop" in g and g["max_drop"] != "":
        config.gate.max_drop = float(g["max_drop"])
        sources["gate.max_drop"] = source
    if "fail_on_regression" in g:
        config.gate.fail_on_regression = bool(g["fail_on_regression"])
        sources["gate.fail_on_regression"] = source


def _merge_notify(data: Dict[str, Any], config: NotifyConfig, source: str, sources: Dict[str, str]) -> None:
    n = data.get("notify", {})
    for key in ("slack_url", "discord_url", "webhook_url", "on"):
        if key in n and n[key] != "":
            setattr(config, key, n[key])
            sources[f"notify.{key}"] = source


def _merge_run(data: Dict[str, Any], config: RunConfig, source: str, sources: Dict[str, str]) -> None:
    r = data.get("run", {})
    if "output_dir" in r:
        config.output_dir = r["output_dir"]
        sources["run.output_dir"] = source
    if "label" in r and r["label"]:
        config.label = r["label"]
        sources["run.label"] = source
    if "record_history" in r:
        config.record_history = bool(r["record_history"])
        sources["run.record_history"] = source


def _merge_sweep(data: Dict[str, Any], config: SweepConfig, source: str, sources: Dict[str, str]) -> None:
    s = data.get("sweep", {})
    if "targets" in s and s["targets"]:
        config.targets = list(s["targets"])
        sources["sweep.targets"] = source
    if "sort_by" in s:
        config.sort_by = s["sort_by"]
        sources["sweep.sort_by"] = source
    if "limit" in s:
        config.limit = int(s["limit"])
        sources["sweep.limit"] = source


def _merge_score(data: Dict[str, Any], config: ScoreConfig, source: str, sources: Dict[str, str]) -> None:
    s = data.get("score", {})
    w = s.get("weights", {})
    for key in ("coderace", "agentlint", "agentmd", "agentreflect"):
        if key in w:
            setattr(config.weights, key, float(w[key]))
            sources[f"score.weights.{key}"] = source


def load_config(start: Optional[Path] = None) -> AgentKitConfig:
    """Load and merge config from all sources.

    Precedence (lowest to highest):
    1. Built-in defaults
    2. User config (~/.config/agentkit/config.toml)
    3. Project config (.agentkit.toml)
    4. Environment variables
    """
    config = AgentKitConfig()
    sources: Dict[str, str] = {}

    # 1. User-level config
    if USER_CONFIG_FILE.exists():
        user_data = _parse_toml(USER_CONFIG_FILE)
        _merge_gate(user_data, config, "[user]", sources)
        _merge_notify(user_data, config.notify, "[user]", sources)
        _merge_run(user_data, config.run, "[user]", sources)
        _merge_sweep(user_data, config.sweep, "[user]", sources)
        _merge_score(user_data, config.score, "[user]", sources)

    # 2. Project config
    project_toml = _find_toml_config(start)
    if project_toml:
        project_data = _parse_toml(project_toml)
        _merge_gate(project_data, config, "[project]", sources)
        _merge_notify(project_data, config.notify, "[project]", sources)
        _merge_run(project_data, config.run, "[project]", sources)
        _merge_sweep(project_data, config.sweep, "[project]", sources)
        _merge_score(project_data, config.score, "[project]", sources)

    # 3. Environment variable overrides
    env_map = {
        "AGENTKIT_GATE_MIN_SCORE": ("gate", "min_score", float),
        "AGENTKIT_GATE_MAX_DROP": ("gate", "max_drop", float),
        "AGENTKIT_GATE_FAIL_ON_REGRESSION": ("gate", "fail_on_regression", lambda v: v.lower() in ("1", "true", "yes")),
        "AGENTKIT_SLACK_URL": ("notify", "slack_url", str),
        "AGENTKIT_DISCORD_URL": ("notify", "discord_url", str),
        "AGENTKIT_WEBHOOK_URL": ("notify", "webhook_url", str),
        "AGENTKIT_NOTIFY_ON": ("notify", "on", str),
        "AGENTKIT_OUTPUT_DIR": ("run", "output_dir", str),
        "AGENTKIT_LABEL": ("run", "label", str),
        "AGENTKIT_RECORD_HISTORY": ("run", "record_history", lambda v: v.lower() in ("1", "true", "yes")),
        "AGENTKIT_SWEEP_SORT_BY": ("sweep", "sort_by", str),
        "AGENTKIT_SWEEP_LIMIT": ("sweep", "limit", int),
    }
    for env_key, (section, attr, cast) in env_map.items():
        val = os.environ.get(env_key)
        if val is not None:
            try:
                obj = getattr(config, section)
                setattr(obj, attr, cast(val))
                sources[f"{section}.{attr}"] = "[env]"
            except (ValueError, TypeError):
                pass

    config._sources = sources
    return config


def get_config_value(key: str, start: Optional[Path] = None) -> Any:
    """Get a single config value by dotted key (e.g. 'gate.min_score')."""
    config = load_config(start)
    parts = key.split(".")
    obj: Any = config
    for part in parts:
        if hasattr(obj, part):
            obj = getattr(obj, part)
        else:
            return None
    return obj


def set_config_value(key: str, value: str, path: Path) -> None:
    """Set a key in a TOML config file (creates file if absent)."""
    # Read existing
    existing: Dict[str, Any] = {}
    if path.exists():
        if tomllib is not None:
            existing = _parse_toml(path)
        else:
            existing = {}

    # Navigate/create nested dict
    parts = key.split(".")
    obj = existing
    for part in parts[:-1]:
        if part not in obj:
            obj[part] = {}
        obj = obj[part]

    # Type coercion
    final_key = parts[-1]
    raw = value.strip()
    if raw.lower() in ("true", "yes"):
        obj[final_key] = True
    elif raw.lower() in ("false", "no"):
        obj[final_key] = False
    else:
        try:
            obj[final_key] = int(raw)
        except ValueError:
            try:
                obj[final_key] = float(raw)
            except ValueError:
                obj[final_key] = raw

    # Write back as TOML
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(_dict_to_toml(existing))


def _dict_to_toml(data: Dict[str, Any], prefix: str = "") -> str:
    """Minimal TOML serializer for simple nested dicts."""
    lines = []
    scalars = {k: v for k, v in data.items() if not isinstance(v, dict)}
    tables = {k: v for k, v in data.items() if isinstance(v, dict)}

    for k, v in scalars.items():
        if isinstance(v, bool):
            lines.append(f"{k} = {str(v).lower()}")
        elif isinstance(v, str):
            lines.append(f'{k} = "{v}"')
        elif isinstance(v, list):
            items = ", ".join(f'"{x}"' if isinstance(x, str) else str(x) for x in v)
            lines.append(f"{k} = [{items}]")
        else:
            lines.append(f"{k} = {v}")

    for k, v in tables.items():
        section = f"{prefix}.{k}" if prefix else k
        lines.append("")
        lines.append(f"[{section}]")
        lines.append(_dict_to_toml(v, section).strip())

    return "\n".join(lines) + "\n" if lines else ""
