"""Tests for brynq.knowledge_mcp — shared knowledge graph for the swarm."""

import json
import pytest
from pathlib import Path


@pytest.fixture(autouse=True)
def _isolate_knowledge(tmp_path, monkeypatch):
    """Redirect knowledge storage to a temp directory."""
    knowledge_dir = tmp_path / "knowledge"
    knowledge_dir.mkdir()
    index_path = knowledge_dir / "index.jsonl"
    monkeypatch.setattr("brynq.knowledge_mcp.KNOWLEDGE_DIR", knowledge_dir)
    monkeypatch.setattr("brynq.knowledge_mcp.INDEX_PATH", index_path)


# ── store_knowledge ────────────────────────────────────────────────────────


class TestStoreKnowledge:
    def test_returns_knowledge_id(self):
        from brynq.knowledge_mcp import store_knowledge
        kid = store_knowledge("Test Topic", "Some content", "agent-1")
        assert isinstance(kid, str)
        assert len(kid) == 12

    def test_creates_json_file(self):
        from brynq.knowledge_mcp import store_knowledge, KNOWLEDGE_DIR
        kid = store_knowledge("Topic", "Content", "agent-1")
        assert (KNOWLEDGE_DIR / f"{kid}.json").exists()

    def test_appends_to_index(self):
        from brynq.knowledge_mcp import store_knowledge, INDEX_PATH
        store_knowledge("T1", "C1", "a1")
        store_knowledge("T2", "C2", "a2")
        lines = INDEX_PATH.read_text("utf-8").strip().splitlines()
        assert len(lines) == 2

    def test_entry_has_correct_fields(self):
        from brynq.knowledge_mcp import store_knowledge, get_knowledge
        kid = store_knowledge("My Topic", "My content", "agent-x", tags=["python", "bug"],
                              category="bug", confidence=0.8)
        entry = get_knowledge(kid)
        assert entry["topic"] == "My Topic"
        assert entry["content"] == "My content"
        assert entry["author_id"] == "agent-x"
        assert entry["tags"] == ["python", "bug"]
        assert entry["category"] == "bug"
        assert entry["confidence"] == 0.8
        assert entry["archived"] is False

    def test_default_values(self):
        from brynq.knowledge_mcp import store_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1")
        entry = get_knowledge(kid)
        assert entry["category"] == "general"
        assert entry["confidence"] == 1.0
        assert entry["tags"] == []

    def test_invalid_category_raises(self):
        from brynq.knowledge_mcp import store_knowledge
        with pytest.raises(ValueError, match="Invalid category"):
            store_knowledge("T", "C", "a", category="invalid")

    def test_invalid_confidence_raises(self):
        from brynq.knowledge_mcp import store_knowledge
        with pytest.raises(ValueError, match="Confidence"):
            store_knowledge("T", "C", "a", confidence=1.5)

    def test_empty_topic_raises(self):
        from brynq.knowledge_mcp import store_knowledge
        with pytest.raises(ValueError, match="Topic"):
            store_knowledge("", "Content", "a")

    def test_empty_content_raises(self):
        from brynq.knowledge_mcp import store_knowledge
        with pytest.raises(ValueError, match="Content"):
            store_knowledge("Topic", "   ", "a")


# ── query_knowledge ────────────────────────────────────────────────────────


class TestQueryKnowledge:
    def test_basic_keyword_match(self):
        from brynq.knowledge_mcp import store_knowledge, query_knowledge
        store_knowledge("Python setup guide", "How to install Python and pip", "a1", tags=["python"])
        store_knowledge("JavaScript basics", "Introduction to JS", "a1", tags=["js"])
        results = query_knowledge("python install")
        assert len(results) >= 1
        assert results[0]["topic"] == "Python setup guide"

    def test_returns_relevance_scores(self):
        from brynq.knowledge_mcp import store_knowledge, query_knowledge
        store_knowledge("Database design", "SQL schema best practices", "a1")
        results = query_knowledge("database sql")
        assert len(results) >= 1
        assert "relevance_score" in results[0]
        assert results[0]["relevance_score"] > 0

    def test_filters_by_category(self):
        from brynq.knowledge_mcp import store_knowledge, query_knowledge
        store_knowledge("API design", "REST API patterns", "a1", category="architecture")
        store_knowledge("API bug", "API returns 500", "a1", category="bug")
        results = query_knowledge("API", category="bug")
        assert len(results) == 1
        assert results[0]["category"] == "bug"

    def test_filters_by_tags(self):
        from brynq.knowledge_mcp import store_knowledge, query_knowledge
        store_knowledge("Setup", "Install deps", "a1", tags=["devops"])
        store_knowledge("Setup", "Install deps", "a1", tags=["frontend"])
        results = query_knowledge("setup install", tags=["devops"])
        assert len(results) == 1

    def test_filters_by_min_confidence(self):
        from brynq.knowledge_mcp import store_knowledge, query_knowledge
        store_knowledge("Topic A", "Content A", "a1", confidence=0.3)
        store_knowledge("Topic A high", "Content A high", "a1", confidence=0.9)
        results = query_knowledge("topic content", min_confidence=0.5)
        assert all(r["confidence"] >= 0.5 for r in results)

    def test_limit_results(self):
        from brynq.knowledge_mcp import store_knowledge, query_knowledge
        for i in range(10):
            store_knowledge(f"Widget topic {i}", f"Widget content {i}", "a1")
        results = query_knowledge("widget", limit=3)
        assert len(results) == 3

    def test_excludes_archived(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, query_knowledge
        kid = store_knowledge("Archived topic", "Archived content", "a1")
        delete_knowledge(kid)
        results = query_knowledge("archived")
        assert len(results) == 0

    def test_empty_query_returns_empty(self):
        from brynq.knowledge_mcp import store_knowledge, query_knowledge
        store_knowledge("Something", "Content here", "a1")
        results = query_knowledge("")
        assert results == []


# ── get_knowledge ──────────────────────────────────────────────────────────


class TestGetKnowledge:
    def test_returns_entry(self):
        from brynq.knowledge_mcp import store_knowledge, get_knowledge
        kid = store_knowledge("Topic", "Content", "a1")
        entry = get_knowledge(kid)
        assert entry is not None
        assert entry["knowledge_id"] == kid

    def test_nonexistent_returns_none(self):
        from brynq.knowledge_mcp import get_knowledge
        assert get_knowledge("nonexistent") is None

    def test_archived_returns_none(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1")
        delete_knowledge(kid)
        assert get_knowledge(kid) is None


# ── update_knowledge ───────────────────────────────────────────────────────


class TestUpdateKnowledge:
    def test_update_content(self):
        from brynq.knowledge_mcp import store_knowledge, update_knowledge, get_knowledge
        kid = store_knowledge("T", "Old content", "a1")
        update_knowledge(kid, content="New content")
        entry = get_knowledge(kid)
        assert entry["content"] == "New content"

    def test_update_confidence(self):
        from brynq.knowledge_mcp import store_knowledge, update_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1", confidence=0.5)
        update_knowledge(kid, confidence=0.9)
        entry = get_knowledge(kid)
        assert entry["confidence"] == 0.9

    def test_update_tags(self):
        from brynq.knowledge_mcp import store_knowledge, update_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1", tags=["old"])
        update_knowledge(kid, tags=["new", "updated"])
        entry = get_knowledge(kid)
        assert entry["tags"] == ["new", "updated"]

    def test_tracks_revision_history(self):
        from brynq.knowledge_mcp import store_knowledge, update_knowledge, get_knowledge
        kid = store_knowledge("T", "V1", "a1", confidence=0.5)
        update_knowledge(kid, content="V2", confidence=0.7)
        entry = get_knowledge(kid)
        assert len(entry["revisions"]) == 1
        assert entry["revisions"][0]["content"] == "V1"
        assert entry["revisions"][0]["confidence"] == 0.5

    def test_multiple_revisions(self):
        from brynq.knowledge_mcp import store_knowledge, update_knowledge, get_knowledge
        kid = store_knowledge("T", "V1", "a1")
        update_knowledge(kid, content="V2")
        update_knowledge(kid, content="V3")
        entry = get_knowledge(kid)
        assert len(entry["revisions"]) == 2

    def test_nonexistent_raises(self):
        from brynq.knowledge_mcp import update_knowledge
        with pytest.raises(FileNotFoundError):
            update_knowledge("nonexistent", content="X")

    def test_archived_raises(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, update_knowledge
        kid = store_knowledge("T", "C", "a1")
        delete_knowledge(kid)
        with pytest.raises(ValueError, match="archived"):
            update_knowledge(kid, content="X")

    def test_empty_content_raises(self):
        from brynq.knowledge_mcp import store_knowledge, update_knowledge
        kid = store_knowledge("T", "C", "a1")
        with pytest.raises(ValueError, match="Content"):
            update_knowledge(kid, content="  ")

    def test_invalid_confidence_raises(self):
        from brynq.knowledge_mcp import store_knowledge, update_knowledge
        kid = store_knowledge("T", "C", "a1")
        with pytest.raises(ValueError, match="Confidence"):
            update_knowledge(kid, confidence=-0.1)


# ── delete_knowledge ───────────────────────────────────────────────────────


class TestDeleteKnowledge:
    def test_soft_delete(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, _read_entry
        kid = store_knowledge("T", "C", "a1")
        assert delete_knowledge(kid) is True
        entry = _read_entry(kid)
        assert entry["archived"] is True
        assert "archived_at" in entry

    def test_nonexistent_returns_false(self):
        from brynq.knowledge_mcp import delete_knowledge
        assert delete_knowledge("nonexistent") is False

    def test_double_delete_returns_false(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge
        kid = store_knowledge("T", "C", "a1")
        assert delete_knowledge(kid) is True
        assert delete_knowledge(kid) is False


# ── list_topics ────────────────────────────────────────────────────────────


class TestListTopics:
    def test_empty(self):
        from brynq.knowledge_mcp import list_topics
        assert list_topics() == []

    def test_lists_active_topics(self):
        from brynq.knowledge_mcp import store_knowledge, list_topics
        store_knowledge("Alpha", "A content", "a1", category="bug")
        store_knowledge("Beta", "B content", "a2", category="feature")
        topics = list_topics()
        assert len(topics) == 2

    def test_filters_by_category(self):
        from brynq.knowledge_mcp import store_knowledge, list_topics
        store_knowledge("A", "C", "a1", category="bug")
        store_knowledge("B", "C", "a1", category="feature")
        topics = list_topics(category="bug")
        assert len(topics) == 1
        assert topics[0]["category"] == "bug"

    def test_excludes_archived(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, list_topics
        kid = store_knowledge("T", "C", "a1")
        store_knowledge("T2", "C2", "a2")
        delete_knowledge(kid)
        topics = list_topics()
        assert len(topics) == 1


# ── get_context_for_task ───────────────────────────────────────────────────


class TestGetContextForTask:
    def test_finds_relevant_entries(self):
        from brynq.knowledge_mcp import store_knowledge, get_context_for_task
        store_knowledge("Python testing", "Use pytest for unit tests", "a1", tags=["testing"])
        store_knowledge("Database migration", "Use alembic for migrations", "a1", tags=["db"])
        results = get_context_for_task("write unit tests with pytest")
        assert len(results) >= 1
        assert results[0]["topic"] == "Python testing"

    def test_respects_limit(self):
        from brynq.knowledge_mcp import store_knowledge, get_context_for_task
        for i in range(10):
            store_knowledge(f"Task topic {i}", f"Task content {i}", "a1")
        results = get_context_for_task("task topic content", limit=2)
        assert len(results) == 2

    def test_excludes_archived(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, get_context_for_task
        kid = store_knowledge("Archived task info", "Task archived content", "a1")
        delete_knowledge(kid)
        results = get_context_for_task("archived task")
        assert len(results) == 0


# ── verify_knowledge ───────────────────────────────────────────────────────


class TestVerifyKnowledge:
    def test_verify_boosts_confidence(self):
        from brynq.knowledge_mcp import store_knowledge, verify_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1", confidence=0.5)
        verify_knowledge(kid, "verifier-1", is_accurate=True)
        entry = get_knowledge(kid)
        assert entry["confidence"] == pytest.approx(0.6, abs=0.01)

    def test_dispute_reduces_confidence(self):
        from brynq.knowledge_mcp import store_knowledge, verify_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1", confidence=0.5)
        verify_knowledge(kid, "verifier-1", is_accurate=False)
        entry = get_knowledge(kid)
        assert entry["confidence"] == pytest.approx(0.35, abs=0.01)

    def test_confidence_capped_at_1(self):
        from brynq.knowledge_mcp import store_knowledge, verify_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1", confidence=0.95)
        verify_knowledge(kid, "v1", is_accurate=True)
        entry = get_knowledge(kid)
        assert entry["confidence"] <= 1.0

    def test_confidence_floored_at_0(self):
        from brynq.knowledge_mcp import store_knowledge, verify_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1", confidence=0.05)
        verify_knowledge(kid, "v1", is_accurate=False)
        entry = get_knowledge(kid)
        assert entry["confidence"] >= 0.0

    def test_records_verification(self):
        from brynq.knowledge_mcp import store_knowledge, verify_knowledge, get_knowledge
        kid = store_knowledge("T", "C", "a1")
        verify_knowledge(kid, "v1", is_accurate=True)
        verify_knowledge(kid, "v2", is_accurate=False)
        entry = get_knowledge(kid)
        assert len(entry["verifications"]) == 2
        assert entry["verifications"][0]["verifier_id"] == "v1"
        assert entry["verifications"][1]["is_accurate"] is False

    def test_nonexistent_raises(self):
        from brynq.knowledge_mcp import verify_knowledge
        with pytest.raises(FileNotFoundError):
            verify_knowledge("nonexistent", "v1")

    def test_archived_raises(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, verify_knowledge
        kid = store_knowledge("T", "C", "a1")
        delete_knowledge(kid)
        with pytest.raises(ValueError, match="archived"):
            verify_knowledge(kid, "v1")


# ── get_knowledge_stats ────────────────────────────────────────────────────


class TestGetKnowledgeStats:
    def test_empty_stats(self):
        from brynq.knowledge_mcp import get_knowledge_stats
        stats = get_knowledge_stats()
        assert stats["total_entries"] == 0
        assert stats["active_entries"] == 0

    def test_counts_by_category(self):
        from brynq.knowledge_mcp import store_knowledge, get_knowledge_stats
        store_knowledge("A", "C", "a1", category="bug")
        store_knowledge("B", "C", "a1", category="bug")
        store_knowledge("C", "C", "a2", category="feature")
        stats = get_knowledge_stats()
        assert stats["total_entries"] == 3
        assert stats["by_category"]["bug"] == 2
        assert stats["by_category"]["feature"] == 1

    def test_top_contributors(self):
        from brynq.knowledge_mcp import store_knowledge, get_knowledge_stats
        for i in range(5):
            store_knowledge(f"T{i}", f"C{i}", "prolific-agent")
        store_knowledge("T", "C", "other-agent")
        stats = get_knowledge_stats()
        assert stats["top_contributors"][0]["author_id"] == "prolific-agent"
        assert stats["top_contributors"][0]["count"] == 5

    def test_verification_rate(self):
        from brynq.knowledge_mcp import store_knowledge, verify_knowledge, get_knowledge_stats
        kid1 = store_knowledge("T1", "C1", "a1")
        store_knowledge("T2", "C2", "a1")
        verify_knowledge(kid1, "v1")
        stats = get_knowledge_stats()
        assert stats["verified_count"] == 1
        assert stats["verification_rate"] == 50.0

    def test_archived_counted_separately(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, get_knowledge_stats
        kid = store_knowledge("T", "C", "a1")
        store_knowledge("T2", "C2", "a1")
        delete_knowledge(kid)
        stats = get_knowledge_stats()
        assert stats["total_entries"] == 2
        assert stats["active_entries"] == 1
        assert stats["archived_entries"] == 1


# ── export_knowledge ───────────────────────────────────────────────────────


class TestExportKnowledge:
    def test_export_json(self):
        from brynq.knowledge_mcp import store_knowledge, export_knowledge
        store_knowledge("T1", "C1", "a1", category="bug")
        store_knowledge("T2", "C2", "a2", category="feature")
        result = export_knowledge(format="json")
        data = json.loads(result)
        assert len(data) == 2

    def test_export_json_with_category_filter(self):
        from brynq.knowledge_mcp import store_knowledge, export_knowledge
        store_knowledge("T1", "C1", "a1", category="bug")
        store_knowledge("T2", "C2", "a2", category="feature")
        result = export_knowledge(category="bug", format="json")
        data = json.loads(result)
        assert len(data) == 1
        assert data[0]["category"] == "bug"

    def test_export_csv(self):
        from brynq.knowledge_mcp import store_knowledge, export_knowledge
        store_knowledge("CSV Topic", "CSV Content", "a1", tags=["tag1", "tag2"])
        result = export_knowledge(format="csv")
        assert "knowledge_id" in result
        assert "CSV Topic" in result
        assert "tag1;tag2" in result

    def test_export_csv_empty(self):
        from brynq.knowledge_mcp import export_knowledge
        result = export_knowledge(format="csv")
        assert result == ""

    def test_export_excludes_archived(self):
        from brynq.knowledge_mcp import store_knowledge, delete_knowledge, export_knowledge
        kid = store_knowledge("T", "C", "a1")
        store_knowledge("T2", "C2", "a2")
        delete_knowledge(kid)
        result = export_knowledge(format="json")
        data = json.loads(result)
        assert len(data) == 1

    def test_invalid_format_raises(self):
        from brynq.knowledge_mcp import export_knowledge
        with pytest.raises(ValueError, match="Invalid format"):
            export_knowledge(format="xml")
