"""Auto-bootstrap: install neo-cortex hooks into .claude/settings.json.

When the MCP server starts in a project, this module ensures that
SessionStart and Stop hooks are configured for neo-cortex-timeline
and neo-cortex-stop-hook. Existing settings are preserved (merge, not overwrite).
"""

from __future__ import annotations

import json
import os
from dataclasses import dataclass, field
from pathlib import Path


@dataclass
class BootstrapResult:
    """What the bootstrap did."""

    project_root: str = ""
    settings_path: str = ""
    session_start_installed: bool = False
    stop_installed: bool = False
    already_present: bool = False
    created_dir: bool = False
    created_file: bool = False
    error: str | None = None


_SESSION_START_HOOK = {
    "matcher": "*",
    "hooks": [
        {
            "type": "command",
            "command": "uvx --from neo-cortex-mcp@latest neo-cortex-timeline --n 10",
            "timeout": 15,
        }
    ],
}

_STOP_HOOK = {
    "matcher": "*",
    "hooks": [
        {
            "type": "command",
            "command": "uvx --from neo-cortex-mcp@latest neo-cortex-stop-hook",
            "timeout": 10,
        }
    ],
}


def _has_hook(hook_list: list, marker: str) -> bool:
    """Check if any hook entry contains the marker string in its command."""
    for entry in hook_list:
        for hook in entry.get("hooks", []):
            if marker in hook.get("command", ""):
                return True
    return False


def ensure_hooks(project_root: Path | None = None) -> BootstrapResult:
    """Ensure neo-cortex hooks are installed in .claude/settings.json.

    Safe to call multiple times (idempotent). Merges with existing
    settings — never overwrites permissions or other hooks.
    """
    root = project_root or Path.cwd()
    result = BootstrapResult(project_root=str(root))

    if not (root / ".mcp.json").exists():
        result.error = "no .mcp.json at project root"
        return result

    claude_dir = root / ".claude"
    settings_path = claude_dir / "settings.json"
    result.settings_path = str(settings_path)

    if not claude_dir.exists():
        try:
            claude_dir.mkdir(parents=True, exist_ok=True)
            result.created_dir = True
        except OSError as exc:
            result.error = f"cannot create .claude/: {exc}"
            return result

    data: dict = {}
    if settings_path.exists():
        try:
            raw = settings_path.read_text(encoding="utf-8")
            data = json.loads(raw) if raw.strip() else {}
            if not isinstance(data, dict):
                data = {}
        except (json.JSONDecodeError, OSError):
            data = {}
    else:
        result.created_file = True

    hooks = data.setdefault("hooks", {})
    if not isinstance(hooks, dict):
        hooks = {}
        data["hooks"] = hooks

    session_start = hooks.setdefault("SessionStart", [])
    if not isinstance(session_start, list):
        session_start = []
        hooks["SessionStart"] = session_start

    stop = hooks.setdefault("Stop", [])
    if not isinstance(stop, list):
        stop = []
        hooks["Stop"] = stop

    has_timeline = _has_hook(session_start, "neo-cortex-timeline")
    has_stop = _has_hook(stop, "neo-cortex-stop-hook")

    if has_timeline and has_stop:
        result.already_present = True
        return result

    if not has_timeline:
        session_start.append(_SESSION_START_HOOK)
        result.session_start_installed = True

    if not has_stop:
        stop.append(_STOP_HOOK)
        result.stop_installed = True

    try:
        settings_path.write_text(
            json.dumps(data, indent=2) + "\n",
            encoding="utf-8",
        )
    except OSError as exc:
        result.error = f"cannot write settings.json: {exc}"

    return result
