"""Tests for knowledge retrieval — domain, structural, keyword search."""

import pytest

from fliiq.runtime.knowledge.models import Anchor, KnowledgeRecord, RecordType, SourceType
from fliiq.runtime.knowledge.retrieval import (
    format_records_for_prompt,
    retrieve_by_domain,
    retrieve_by_file,
    search_knowledge,
)
from fliiq.runtime.knowledge.store import KnowledgeStore


@pytest.fixture
def store(tmp_path):
    s = KnowledgeStore(tmp_path / "knowledge")
    s.ensure_dirs()
    return s


def _make(store, **overrides) -> KnowledgeRecord:
    defaults = dict(
        type=RecordType.DECISION,
        source=SourceType.HUMAN_IN_LOOP,
        context="test context",
        decision="test decision",
        rationale="test rationale",
    )
    defaults.update(overrides)
    rec = KnowledgeRecord(**defaults)
    store.save(rec)
    return rec


class TestDomainRetrieval:
    def test_basic(self, store):
        _make(store, domain="coding", decision="Use Python")
        _make(store, domain="business", decision="Use Stripe")
        results = retrieve_by_domain(store, "coding")
        assert len(results) == 1
        assert results[0].decision == "Use Python"

    def test_sorted_by_confidence(self, store):
        _make(store, domain="coding", confidence=0.5, decision="low")
        _make(store, domain="coding", confidence=0.9, decision="high")
        results = retrieve_by_domain(store, "coding")
        assert results[0].decision == "high"

    def test_excludes_expired(self, store):
        rec = _make(store, domain="coding")
        store.delete(rec.id)
        assert retrieve_by_domain(store, "coding") == []

    def test_respects_limit(self, store):
        for i in range(5):
            _make(store, domain="coding")
        assert len(retrieve_by_domain(store, "coding", max_records=2)) == 2


class TestStructuralRetrieval:
    def test_basic(self, store, tmp_path):
        # Create a file to anchor to
        code_file = tmp_path / "src" / "main.py"
        code_file.parent.mkdir(parents=True)
        code_file.write_text("line1\nline2\nline3\n")

        _make(store, anchor=Anchor(file=str(code_file), line_start=1, line_end=3))
        results = retrieve_by_file(store, str(code_file))
        assert len(results) == 1
        record, drift_info = results[0]
        # No content_hash set, so unanchored
        assert drift_info["status"] == "unanchored"

    def test_filters_by_line_range(self, store):
        _make(store, anchor=Anchor(file="src/main.py", line_start=10, line_end=20))
        _make(store, anchor=Anchor(file="src/main.py", line_start=50, line_end=60))

        # Only the first record overlaps lines 15-25
        results = retrieve_by_file(store, "src/main.py", line_range=(15, 25))
        assert len(results) == 1

    def test_empty_for_unanchored_file(self, store):
        _make(store, anchor=Anchor(file="other.py"))
        assert retrieve_by_file(store, "nonexistent.py") == []


class TestKeywordSearch:
    def test_basic(self, store):
        _make(store, decision="Use Twilio for SMS", rationale="Best pricing")
        _make(store, decision="Use Stripe for payments", rationale="Easy integration")
        results = search_knowledge(store, "Twilio SMS pricing")
        assert len(results) >= 1
        assert "Twilio" in results[0].decision

    def test_filters_by_domain(self, store):
        _make(store, decision="Use Python", domain="coding")
        _make(store, decision="Use Python tools", domain="business")
        results = search_knowledge(store, "Python", domain="coding")
        assert len(results) == 1
        assert results[0].domain == "coding"

    def test_filters_by_type(self, store):
        _make(store, decision="Always validate input", type=RecordType.HEURISTIC)
        _make(store, decision="Validate user input", type=RecordType.CONSTRAINT)
        results = search_knowledge(store, "validate input", record_type="heuristic")
        assert len(results) == 1
        assert results[0].type == RecordType.HEURISTIC

    def test_filters_by_tags(self, store):
        _make(store, decision="Use REST", tags=["api", "design"])
        _make(store, decision="Use GraphQL", tags=["api", "frontend"])
        results = search_knowledge(store, "API design", tags=["frontend"])
        assert len(results) == 1
        assert "GraphQL" in results[0].decision

    def test_empty_query_returns_empty(self, store):
        _make(store, decision="Something")
        assert search_knowledge(store, "") == []

    def test_no_match_returns_empty(self, store):
        _make(store, decision="Use Python")
        assert search_knowledge(store, "xyznonexistent") == []


class TestFormatRecords:
    def test_basic_format(self, store):
        rec = _make(store, decision="Use Twilio", domain="infrastructure", confidence=0.9)
        output = format_records_for_prompt([rec])
        assert "Use Twilio" in output
        assert "infrastructure" in output
        assert "0.9" in output
        assert rec.id in output

    def test_with_drift_info(self, store):
        rec = _make(store, decision="Use Twilio", anchor=Anchor(file="src/sms.py"))
        output = format_records_for_prompt([(rec, {"status": "drifted"})])
        assert "DRIFTED" in output

    def test_max_chars_limit(self, store):
        records = [_make(store, decision=f"Decision {i}" * 20) for i in range(10)]
        output = format_records_for_prompt(records, max_chars=200)
        # Should truncate before showing all 10
        assert output.count("[decision]") < 10

    def test_missing_file_warning(self, store):
        rec = _make(store, anchor=Anchor(file="gone.py"))
        output = format_records_for_prompt([(rec, {"status": "missing"})])
        assert "MISSING" in output
