"""Tests for `cyt skills` hook CLI."""

from __future__ import annotations

import json
import tempfile
from io import StringIO
from pathlib import Path
from unittest.mock import patch

import pytest

from cyt.skills import cli as skills_cli


def _write_skill(path: Path, body: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(body, encoding="utf-8")


def _skills_config(root: Path, skills_dir: Path, catalog_dir: Path) -> dict:
    return {
        "skills": {
            "enabled": True,
            "pipeline": "bm25",
            "catalog_dir": str(catalog_dir),
            "directories": [str(skills_dir)],
            "max_tokens_per_request": 4000,
            "pageindex": {"enable_bm25_chunking": True},
        },
        "pruning": {"bm25": {"score_skills": 0.0}},
        "stats": {"database": {"path": str(root / "stats.db")}},
    }


def _write_transcript_with_model(path: Path, model: str) -> None:
    path.write_text(
        json.dumps(
            {
                "type": "assistant",
                "message": {
                    "role": "assistant",
                    "model": model,
                    "content": [{"type": "text", "text": "prior reply"}],
                },
            },
        ),
        encoding="utf-8",
    )


def test_anthropic_user_prompt_resolves_model_from_transcript(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Claude Code: UserPromptSubmit has no model; transcript_path supplies it."""
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        transcript = root / "session.jsonl"
        _write_transcript_with_model(transcript, "google/gemini-3-flash-preview-20251217")
        _write_skill(
            skills_dir / "create-hook.md",
            "---\nname: create-hook\ndescription: Agent hooks for Claude Code sessions.\n---\n"
            "# Create Hook\n\nAgent hooks for Claude Code sessions.\n",
        )

        prompt_payload = {
            "session_id": "11b09b4b-f335-4a08-b618-8f607f6d7a46",
            "transcript_path": str(transcript),
            "cwd": str(root),
            "permission_mode": "default",
            "hook_event_name": "UserPromptSubmit",
            "prompt": "configure agent hooks for sessions",
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(prompt_payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)

        config = _skills_config(root, skills_dir, catalog_dir)
        with patch("cyt.skills.cli.load_config", return_value=config):
            with patch("cyt.config.stats_db_path", return_value=str(root / "stats.db")):
                skills_cli.run(debug=True)

        output = json.loads(stdout.getvalue())
        assert output["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
        assert "<agent-skills>" in output["hookSpecificOutput"]["additionalContext"]

        debug_dir = root / ".debug" / "skills"
        logged = json.loads(next(debug_dir.glob("*.json")).read_text(encoding="utf-8"))
        assert logged["details"]["resolved_model"] == "google/gemini-3-flash-preview-20251217"


def test_nested_payload_user_prompt_submit(monkeypatch: pytest.MonkeyPatch) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        transcript = root / "session.jsonl"
        _write_transcript_with_model(transcript, "claude-sonnet-4")
        _write_skill(
            skills_dir / "create-hook.md",
            "---\nname: create-hook\ndescription: Agent hooks for Claude Code sessions.\n---\n"
            "# Create Hook\n\nAgent hooks for Claude Code sessions.\n",
        )

        payload = {
            "hook_event_name": "UserPromptSubmit",
            "payload": {
                "session_id": "sess-nested",
                "prompt": "configure agent hooks for sessions",
                "transcript_path": str(transcript),
            },
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)

        config = _skills_config(root, skills_dir, catalog_dir)
        with patch("cyt.skills.cli.load_config", return_value=config):
            skills_cli.run()

        output = json.loads(stdout.getvalue())
        assert output["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
        assert "<agent-skills>" in output["hookSpecificOutput"]["additionalContext"]


def test_session_start_is_ignored_without_output(monkeypatch: pytest.MonkeyPatch) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        payload = {
            "hook_event_name": "SessionStart",
            "session_id": "sess-abc",
            "model": "claude-sonnet-4",
            "cwd": str(root),
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)
        monkeypatch.chdir(root)

        config = {"skills": {"enabled": True}}
        with patch("cyt.skills.cli.load_config", return_value=config):
            skills_cli.run(debug=True)

        assert stdout.getvalue() == ""

        debug_dir = root / ".debug" / "skills"
        logged = json.loads(next(debug_dir.glob("*.json")).read_text(encoding="utf-8"))
        assert logged["outcome"] == "session_start_ignored"


def test_user_prompt_emits_json_hook_output(monkeypatch: pytest.MonkeyPatch) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        transcript = root / "session.jsonl"
        _write_transcript_with_model(transcript, "claude-sonnet-4")
        _write_skill(
            skills_dir / "create-hook.md",
            "---\nname: create-hook\ndescription: Agent hooks for Claude Code sessions.\n---\n"
            "# Create Hook\n\nAgent hooks for Claude Code sessions.\n",
        )

        payload = {
            "hook_event_name": "UserPromptSubmit",
            "session_id": "sess-abc",
            "transcript_path": str(transcript),
            "prompt": "configure agent hooks for sessions",
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)

        config = _skills_config(root, skills_dir, catalog_dir)
        with patch("cyt.skills.cli.load_config", return_value=config):
            with patch("cyt.config.stats_db_path", return_value=str(root / "stats.db")):
                skills_cli.run()

        output = json.loads(stdout.getvalue())
        assert output["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
        assert "<agent-skills>" in output["hookSpecificOutput"]["additionalContext"]


def test_codex_user_prompt_uses_payload_model_without_session_start(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        _write_skill(
            skills_dir / "create-hook.md",
            "---\nname: create-hook\ndescription: Agent hooks for Claude Code sessions.\n---\n"
            "# Create Hook\n\nAgent hooks for Claude Code sessions.\n",
        )

        payload = {
            "hook_event_name": "UserPromptSubmit",
            "session_id": "codex-sess",
            "model": "gpt-5.4-mini",
            "prompt": "configure agent hooks for sessions",
            "cwd": str(root),
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)
        monkeypatch.chdir(root)

        config = _skills_config(root, skills_dir, catalog_dir)
        with patch("cyt.skills.cli.load_config", return_value=config):
            with patch("cyt.config.stats_db_path", return_value=str(root / "stats.db")):
                skills_cli.run(debug=True)

        debug_dir = root / ".debug" / "skills"
        logged = json.loads(next(debug_dir.glob("*.json")).read_text(encoding="utf-8"))
        assert logged["details"]["resolved_model"] == "gpt-5.4-mini"

        output = json.loads(stdout.getvalue())
        assert output["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
        assert "<agent-skills>" in output["hookSpecificOutput"]["additionalContext"]


def test_cli_prompt_prints_injection_text(monkeypatch: pytest.MonkeyPatch) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        _write_skill(
            skills_dir / "context7.md",
            "---\nname: context7\ndescription: Use Context7 MCP for up-to-date library documentation.\n---\n"
            "# Context7\n\nUse Context7 MCP for up-to-date library documentation.\n",
        )

        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)
        monkeypatch.chdir(root)

        config = _skills_config(root, skills_dir, catalog_dir)
        with patch("cyt.skills.cli.load_config", return_value=config):
            skills_cli.run(prompt="use context7 library docs")

        output = stdout.getvalue()
        assert "<agent-skills>" in output
        assert "context7" in output
        assert "hookSpecificOutput" not in output


def test_debug_logs_stdin_when_skills_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        payload = {
            "hook_event_name": "SessionStart",
            "session_id": "sess-debug",
            "model": "gpt-4",
            "cwd": str(root),
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)

        config = {"skills": {"enabled": False}}
        with patch("cyt.skills.cli.load_config", return_value=config):
            skills_cli.run(debug=True)

        debug_dir = root / ".debug" / "skills"
        logs = list(debug_dir.glob("*.json"))
        assert len(logs) == 1
        logged = json.loads(logs[0].read_text(encoding="utf-8"))
        assert logged["outcome"] == "skipped_disabled"
        assert logged["stdin_raw"] == json.dumps(payload)
        assert logged["payload"] == payload


def test_cli_prompt_runs_when_skills_disabled_in_config(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        _write_skill(
            skills_dir / "context7.md",
            "---\nname: context7\ndescription: Use Context7 MCP for up-to-date library documentation.\n---\n"
            "# Context7\n\nUse Context7 MCP for up-to-date library documentation.\n",
        )

        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)
        monkeypatch.chdir(root)

        config = {
            "skills": {
                "enabled": False,
                "pipeline": "bm25",
                "catalog_dir": str(catalog_dir),
                "directories": [str(skills_dir)],
                "max_tokens_per_request": 4000,
                "pageindex": {"enable_bm25_chunking": True},
            },
            "pruning": {"bm25": {"score_skills": 0.0}},
        }

        with patch("cyt.skills.cli.load_config", return_value=config):
            skills_cli.run(prompt="use context7 docs")

        assert "<agent-skills>" in stdout.getvalue()


def test_user_prompt_uses_transcript_query_before_search_skills(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Hook enriches search query with last assistant turn from transcript_path."""
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        transcript = root / "session.jsonl"
        transcript.write_text(
            json.dumps(
                {
                    "type": "assistant",
                    "message": {
                        "role": "assistant",
                        "model": "claude-sonnet-4",
                        "content": [{"type": "text", "text": "prior assistant reply"}],
                    },
                },
            ),
            encoding="utf-8",
        )
        _write_skill(
            skills_dir / "create-hook.md",
            "---\nname: create-hook\ndescription: Agent hooks for Claude Code sessions.\n---\n"
            "# Create Hook\n\nAgent hooks for Claude Code sessions.\n",
        )

        payload = {
            "hook_event_name": "UserPromptSubmit",
            "session_id": "sess-transcript",
            "transcript_path": str(transcript),
            "prompt": "configure agent hooks",
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)

        config = _skills_config(root, skills_dir, catalog_dir)
        captured: dict[str, str] = {}

        def _capture_search(query: str, entries: list, *, config: dict) -> list:
            captured["query"] = query
            from cyt.skills.search import search_skills as real_search

            return real_search(query, entries, config=config)

        with patch("cyt.skills.cli.load_config", return_value=config):
            with patch("cyt.config.stats_db_path", return_value=str(root / "stats.db")):
                with patch("cyt.skills.cli.search_skills", side_effect=_capture_search):
                    skills_cli.run()

        assert captured["query"] == (
            "User_Asks: configure agent hooks; Assistant_Says: prior assistant reply"
        )


@pytest.mark.parametrize("pipeline", ["bm25", "rerank", "llm"])
def test_user_prompt_uses_transcript_query_for_all_pipelines(
    monkeypatch: pytest.MonkeyPatch,
    pipeline: str,
) -> None:
    """Hook passes transcript-enriched query to search_skills for every pipeline."""
    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        transcript = root / "session.jsonl"
        transcript.write_text(
            json.dumps(
                {
                    "message": {
                        "role": "assistant",
                        "model": "claude-sonnet-4",
                        "content": [{"type": "text", "text": "prior assistant reply"}],
                    },
                },
            ),
            encoding="utf-8",
        )
        _write_skill(
            skills_dir / "create-hook.md",
            "---\nname: create-hook\ndescription: Agent hooks for Claude Code sessions.\n---\n"
            "# Create Hook\n\nAgent hooks for Claude Code sessions.\n",
        )

        payload = {
            "hook_event_name": "UserPromptSubmit",
            "session_id": "sess-transcript",
            "transcript_path": str(transcript),
            "prompt": "configure agent hooks",
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)

        config = _skills_config(root, skills_dir, catalog_dir)
        config["skills"]["pipeline"] = pipeline
        captured: dict[str, str] = {}

        def _capture_search(query: str, entries: list, *, config: dict) -> list:
            captured["query"] = query
            return []

        with patch("cyt.skills.cli.load_config", return_value=config):
            with patch("cyt.config.stats_db_path", return_value=str(root / "stats.db")):
                with patch("cyt.skills.cli.search_skills", side_effect=_capture_search):
                    skills_cli.run()

        assert captured["query"] == (
            "User_Asks: configure agent hooks; Assistant_Says: prior assistant reply"
        )


def test_disabled_config_is_noop(monkeypatch: pytest.MonkeyPatch) -> None:
    payload = {
        "hook_event_name": "UserPromptSubmit",
        "session_id": "sess-abc",
        "prompt": "hello",
    }
    monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
    stdout = StringIO()
    monkeypatch.setattr("sys.stdout", stdout)

    config = {"skills": {"enabled": False}}
    with patch("cyt.skills.cli.load_config", return_value=config):
        skills_cli.run()

    assert stdout.getvalue() == ""


def test_hook_stdout_is_pure_json_when_search_prints_to_stdout(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Pruning diagnostics must not prefix hook stdout or Codex falls back to plain text."""
    from cyt.indexer.tokens import log_token_usage

    with tempfile.TemporaryDirectory() as tmp:
        root = Path(tmp)
        skills_dir = root / "skills"
        catalog_dir = root / "catalog"
        _write_skill(
            skills_dir / "create-hook.md",
            "---\nname: create-hook\ndescription: Agent hooks for Claude Code sessions.\n---\n"
            "# Create Hook\n\nAgent hooks for Claude Code sessions.\n",
        )

        payload = {
            "hook_event_name": "UserPromptSubmit",
            "session_id": "sess-pure-json",
            "model": "gpt-5",
            "prompt": "configure agent hooks",
            "cwd": str(root),
        }
        monkeypatch.setattr("sys.stdin", StringIO(json.dumps(payload)))
        stdout = StringIO()
        monkeypatch.setattr("sys.stdout", stdout)
        monkeypatch.chdir(root)

        real_search = skills_cli.search_skills

        def _noisy_search(query: str, entries: list, *, config: dict) -> list:
            print("pruning model tokens (llm): 999 tokens", flush=True)
            log_token_usage("pruning model tokens (llm)", 999)
            return real_search(query, entries, config=config)

        config = _skills_config(root, skills_dir, catalog_dir)
        with patch("cyt.skills.cli.load_config", return_value=config):
            with patch("cyt.config.stats_db_path", return_value=str(root / "stats.db")):
                with patch("cyt.skills.cli.search_skills", side_effect=_noisy_search):
                    skills_cli.run()

        raw = stdout.getvalue()
        output = json.loads(raw)
        assert raw.strip().startswith("{")
        assert raw.strip().endswith("}")
        assert output["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
        assert "<agent-skills>" in output["hookSpecificOutput"]["additionalContext"]
        assert "pruning model tokens" not in raw
        assert "hookSpecificOutput" not in output["hookSpecificOutput"]["additionalContext"]
