"""Tests for brynq.rag_pipeline — Phase 62 RAG pipeline with source management."""

import json
import pytest
from pathlib import Path


@pytest.fixture(autouse=True)
def _isolate_rag(tmp_path, monkeypatch):
    """Redirect RAG storage to temp directory."""
    rag_dir = tmp_path / "rag"
    rag_dir.mkdir()
    monkeypatch.setattr("brynq.rag_pipeline.RAG_DIR", rag_dir)
    monkeypatch.setattr("brynq.rag_pipeline.SOURCES_PATH", rag_dir / "sources.json")

    # Also isolate BROKER_DIR for channel/knowledge reads
    broker_dir = tmp_path / "broker"
    broker_dir.mkdir()
    (broker_dir / "channels").mkdir()
    monkeypatch.setattr("brynq.rag_pipeline.BROKER_DIR", broker_dir)


@pytest.fixture
def channel_with_messages(tmp_path, monkeypatch):
    """Create a channel with messages for testing."""
    broker_dir = tmp_path / "broker"
    channels_dir = broker_dir / "channels"
    channels_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr("brynq.rag_pipeline.BROKER_DIR", broker_dir)

    path = channels_dir / "dev.jsonl"
    msgs = [
        {"message": "Deployed python microservice to staging", "session_name": "agent-1"},
        {"message": "Fixed database connection pool leak", "session_name": "agent-2"},
        {"message": "Added unit tests for authentication module", "session_name": "agent-3"},
        {"message": "Python async performance optimization complete", "session_name": "agent-1"},
    ]
    with path.open("w", encoding="utf-8") as f:
        for m in msgs:
            f.write(json.dumps(m) + "\n")
    return "dev"


@pytest.fixture
def file_source(tmp_path):
    """Create a text file source."""
    path = tmp_path / "notes.txt"
    path.write_text(
        "Python is a great language for data science.\n\n"
        "Flask and Django are popular web frameworks.\n\n"
        "Machine learning requires lots of data.\n",
        encoding="utf-8",
    )
    return str(path)


# ── Tokenization ──────────────────────────────────────────────────────────


class TestTokenize:
    def test_basic(self):
        from brynq.rag_pipeline import _tokenize
        assert _tokenize("Hello World") == ["hello", "world"]

    def test_empty(self):
        from brynq.rag_pipeline import _tokenize
        assert _tokenize("") == []

    def test_underscores(self):
        from brynq.rag_pipeline import _tokenize
        assert "my_func" in _tokenize("my_func")


# ── add_source / list_sources / remove_source ─────────────────────────────


class TestSourceManagement:
    def test_add_source(self):
        from brynq.rag_pipeline import add_source
        src = add_source("my_channel", "channel", "dev")
        assert src["name"] == "my_channel"
        assert src["source_type"] == "channel"

    def test_add_source_empty_name_raises(self):
        from brynq.rag_pipeline import add_source
        with pytest.raises(ValueError, match="name"):
            add_source("", "channel", "dev")

    def test_add_source_invalid_type_raises(self):
        from brynq.rag_pipeline import add_source
        with pytest.raises(ValueError, match="source_type"):
            add_source("x", "invalid", "path")

    def test_list_sources_empty(self):
        from brynq.rag_pipeline import list_sources
        assert list_sources() == []

    def test_list_sources_after_add(self):
        from brynq.rag_pipeline import add_source, list_sources
        add_source("s1", "channel", "dev")
        add_source("s2", "file", "/tmp/f.txt")
        sources = list_sources()
        assert len(sources) == 2

    def test_remove_source(self):
        from brynq.rag_pipeline import add_source, remove_source, list_sources
        add_source("rm1", "channel", "test")
        assert remove_source("rm1") is True
        assert len(list_sources()) == 0

    def test_remove_nonexistent(self):
        from brynq.rag_pipeline import remove_source
        assert remove_source("no_such") is False

    def test_add_all_types(self):
        from brynq.rag_pipeline import add_source
        add_source("ch", "channel", "gen")
        add_source("kb", "knowledge_base", "")
        add_source("fl", "file", "/tmp/x.txt")
        # No errors


# ── query_context ─────────────────────────────────────────────────────────


class TestQueryContext:
    def test_empty_query(self):
        from brynq.rag_pipeline import query_context
        assert query_context("") == []

    def test_no_sources_returns_empty(self):
        from brynq.rag_pipeline import query_context
        # No knowledge and no sources = empty
        assert query_context("python") == []

    def test_from_channel(self, channel_with_messages):
        from brynq.rag_pipeline import add_source, query_context
        add_source("dev_channel", "channel", channel_with_messages)
        results = query_context("python microservice")
        assert len(results) >= 1
        assert results[0]["score"] > 0

    def test_from_file(self, file_source):
        from brynq.rag_pipeline import add_source, query_context
        add_source("notes", "file", file_source)
        results = query_context("python data science")
        assert len(results) >= 1

    def test_respects_max_chunks(self, channel_with_messages):
        from brynq.rag_pipeline import add_source, query_context
        add_source("ch", "channel", channel_with_messages)
        results = query_context("python", max_chunks=1)
        assert len(results) <= 1

    def test_filters_by_source(self, channel_with_messages, file_source):
        from brynq.rag_pipeline import add_source, query_context
        add_source("ch", "channel", channel_with_messages)
        add_source("fl", "file", file_source)
        results = query_context("python", sources=["fl"])
        for r in results:
            assert r["source"].startswith("file:")

    def test_scores_sorted(self, channel_with_messages):
        from brynq.rag_pipeline import add_source, query_context
        add_source("ch", "channel", channel_with_messages)
        results = query_context("python microservice staging")
        if len(results) > 1:
            assert results[0]["score"] >= results[1]["score"]


# ── build_prompt ──────────────────────────────────────────────────────────


class TestBuildPrompt:
    def test_with_chunks(self):
        from brynq.rag_pipeline import build_prompt
        chunks = [
            {"source": "channel:dev", "content": "Python is great", "score": 0.9},
            {"source": "file:notes.txt", "content": "Flask framework", "score": 0.5},
        ]
        prompt = build_prompt("What is Python?", chunks)
        assert "Context" in prompt
        assert "Python is great" in prompt
        assert "What is Python?" in prompt

    def test_no_chunks(self):
        from brynq.rag_pipeline import build_prompt
        prompt = build_prompt("Question?", [])
        assert "No relevant context" in prompt
        assert "Question?" in prompt

    def test_prompt_includes_source(self):
        from brynq.rag_pipeline import build_prompt
        chunks = [{"source": "kb:123", "content": "Info", "score": 0.7}]
        prompt = build_prompt("Q", chunks)
        assert "kb:123" in prompt

    def test_prompt_includes_relevance(self):
        from brynq.rag_pipeline import build_prompt
        chunks = [{"source": "x", "content": "y", "score": 0.85}]
        prompt = build_prompt("Q", chunks)
        assert "0.850" in prompt


# ── Internal chunk gathering ──────────────────────────────────────────────


class TestChunkGathering:
    def test_chunks_from_channel(self, channel_with_messages):
        from brynq.rag_pipeline import _chunks_from_channel
        chunks = _chunks_from_channel(channel_with_messages)
        assert len(chunks) == 4

    def test_chunks_from_missing_channel(self):
        from brynq.rag_pipeline import _chunks_from_channel
        assert _chunks_from_channel("nonexistent") == []

    def test_chunks_from_file(self, file_source):
        from brynq.rag_pipeline import _chunks_from_file
        chunks = _chunks_from_file(file_source)
        assert len(chunks) == 3  # 3 paragraphs

    def test_chunks_from_missing_file(self):
        from brynq.rag_pipeline import _chunks_from_file
        assert _chunks_from_file("/no/such/file.txt") == []
