"""Tests for LEARNINGS.md generation.

Covers:
- generate_learnings() content from reflections + memory index
- write_learnings() creates .exo/LEARNINGS.md
- Adapter generation includes LEARNINGS.md
- Adapters reference LEARNINGS.md
- Upgrade regenerates LEARNINGS.md
- Empty repo produces minimal learnings file
- Vendor-neutral: no vendor-specific content in LEARNINGS.md
"""

from __future__ import annotations

from pathlib import Path

from exo.kernel import governance as governance_mod
from exo.kernel.utils import dump_yaml
from exo.stdlib.defaults import DEFAULT_CONFIG, DEFAULT_CONSTITUTION
from exo.stdlib.reflect import (
    LEARNINGS_PATH,
    generate_learnings,
    reflect,
    write_learnings,
)


def _bootstrap_repo(tmp_path: Path) -> Path:
    repo = tmp_path
    exo_dir = repo / ".exo"
    exo_dir.mkdir(parents=True, exist_ok=True)
    constitution = DEFAULT_CONSTITUTION
    (exo_dir / "CONSTITUTION.md").write_text(constitution, encoding="utf-8")
    dump_yaml(exo_dir / "config.yaml", DEFAULT_CONFIG)
    for d in ["tickets", "locks", "logs", "memory", "memory/reflections", "memory/sessions", "cache", "cache/sessions"]:
        (exo_dir / d).mkdir(parents=True, exist_ok=True)
    governance_mod.compile_constitution(repo)
    return repo


# ── Content Generation ─────────────────────────────────────────────


class TestGenerateLearnings:
    def test_empty_repo_produces_header(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        content = generate_learnings(repo)
        assert "# Operational Learnings" in content
        assert "Auto-generated by ExoProtocol" in content
        assert "No active reflections" in content

    def test_with_reflections(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        reflect(repo, pattern="Tests fail on CI", insight="Pin dependency versions", severity="high")
        reflect(repo, pattern="OOM on large files", insight="Stream instead of load", severity="critical")
        content = generate_learnings(repo)
        assert "## Reflections" in content
        assert "Tests fail on CI" in content
        assert "Pin dependency versions" in content
        assert "OOM on large files" in content
        assert "[HIGH]" in content
        assert "[CRITICAL]" in content

    def test_scoped_reflections_show_scope(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        reflect(repo, pattern="Scoped issue", insight="Fix", scope="TICKET-007")
        content = generate_learnings(repo)
        assert "TICKET-007" in content

    def test_tags_included(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        reflect(repo, pattern="Tagged", insight="Fix", tags=["ci", "flaky"])
        content = generate_learnings(repo)
        assert "ci" in content
        assert "flaky" in content

    def test_dismissed_not_in_reflections_section(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        from exo.stdlib.reflect import dismiss_reflection

        ref = reflect(repo, pattern="Old issue", insight="Fixed")
        dismiss_reflection(repo, ref.id)
        content = generate_learnings(repo)
        assert (
            "Old issue" not in content.split("## Known Failure Modes")[0]
            if "## Known Failure Modes" in content
            else "Old issue" not in content.split("---")[0]
        )

    def test_failure_modes_section(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        # reflect() auto-syncs to memory index as a failure mode
        reflect(repo, pattern="Timeout on API calls", insight="Add retry logic", severity="high")
        content = generate_learnings(repo)
        assert "## Known Failure Modes" in content
        assert "Timeout on API calls" in content

    def test_stats_footer(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        reflect(repo, pattern="P1", insight="I1")
        reflect(repo, pattern="P2", insight="I2")
        content = generate_learnings(repo)
        assert "2 active reflection(s)" in content
        assert "0 dismissed" in content

    def test_no_vendor_specific_content(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        reflect(repo, pattern="Test", insight="Fix")
        content = generate_learnings(repo)
        # Should NOT mention any vendor
        lower = content.lower()
        assert "claude" not in lower
        assert "cursor" not in lower
        assert "copilot" not in lower
        assert "anthropic" not in lower


# ── File Writing ──────────────────────────────────────────────────


class TestWriteLearnings:
    def test_creates_file(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        path = write_learnings(repo)
        assert path == str(LEARNINGS_PATH)
        assert (repo / LEARNINGS_PATH).exists()

    def test_content_matches_generate(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        reflect(repo, pattern="P1", insight="I1")
        expected = generate_learnings(repo)
        write_learnings(repo)
        actual = (repo / LEARNINGS_PATH).read_text(encoding="utf-8")
        assert actual == expected

    def test_idempotent(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        write_learnings(repo)
        content1 = (repo / LEARNINGS_PATH).read_text(encoding="utf-8")
        write_learnings(repo)
        content2 = (repo / LEARNINGS_PATH).read_text(encoding="utf-8")
        assert content1 == content2


# ── Adapter Integration ──────────────────────────────────────────


class TestAdapterIntegration:
    def test_adapter_generate_writes_learnings(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        from exo.stdlib.adapters import generate_adapters

        result = generate_adapters(repo)
        assert ".exo/LEARNINGS.md" in result["written"]
        assert (repo / ".exo" / "LEARNINGS.md").exists()

    def test_adapter_generate_dry_run_skips_learnings(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        from exo.stdlib.adapters import generate_adapters

        generate_adapters(repo, dry_run=True)
        assert not (repo / ".exo" / "LEARNINGS.md").exists()

    def test_claude_md_references_learnings(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        from exo.stdlib.adapters import generate_adapters

        generate_adapters(repo)
        claude_md = (repo / "CLAUDE.md").read_text(encoding="utf-8")
        assert ".exo/LEARNINGS.md" in claude_md

    def test_cursorrules_references_learnings(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        from exo.stdlib.adapters import generate_adapters

        generate_adapters(repo)
        cursorrules = (repo / ".cursorrules").read_text(encoding="utf-8")
        assert ".exo/LEARNINGS.md" in cursorrules

    def test_agents_md_references_learnings(self, tmp_path: Path) -> None:
        repo = _bootstrap_repo(tmp_path)
        from exo.stdlib.adapters import generate_adapters

        generate_adapters(repo)
        agents_md = (repo / "AGENTS.md").read_text(encoding="utf-8")
        assert ".exo/LEARNINGS.md" in agents_md


# ── Upgrade Integration ──────────────────────────────────────────


class TestUpgradeIntegration:
    def test_upgrade_regenerates_learnings(self, tmp_path: Path) -> None:
        from exo.stdlib.engine import KernelEngine

        engine = KernelEngine(repo=tmp_path, actor="test-agent")
        engine.init(scan=False)
        # Add a reflection
        reflect(tmp_path, pattern="Upgrade test", insight="Should appear")
        # Run upgrade (which regenerates adapters → LEARNINGS.md)
        from exo.stdlib.upgrade import upgrade

        upgrade(tmp_path)
        learnings = (tmp_path / ".exo" / "LEARNINGS.md").read_text(encoding="utf-8")
        assert "Upgrade test" in learnings
