"""Tests for knowledge store — filesystem CRUD + indexes."""

import json

import pytest

from fliiq.runtime.knowledge.models import Anchor, KnowledgeRecord, RecordType, SourceType
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_record(**overrides) -> KnowledgeRecord:
    defaults = dict(
        type=RecordType.DECISION,
        source=SourceType.HUMAN_IN_LOOP,
        context="test context",
        decision="test decision",
        rationale="test rationale",
    )
    defaults.update(overrides)
    return KnowledgeRecord(**defaults)


class TestCRUD:
    def test_save_and_load(self, store):
        rec = _make_record()
        store.save(rec)
        loaded = store.load(rec.id)
        assert loaded is not None
        assert loaded.id == rec.id
        assert loaded.decision == "test decision"

    def test_load_missing(self, store):
        assert store.load("kr_nonexistent") is None

    def test_update(self, store):
        rec = _make_record(confidence=0.5)
        store.save(rec)
        updated = store.update(rec.id, {"confidence": 0.9, "decision": "updated decision"})
        assert updated.confidence == 0.9
        assert updated.decision == "updated decision"
        assert updated.updated_at > rec.updated_at

    def test_update_missing(self, store):
        assert store.update("kr_nonexistent", {"confidence": 0.5}) is None

    def test_delete_soft(self, store):
        rec = _make_record()
        store.save(rec)
        assert store.delete(rec.id) is True
        loaded = store.load(rec.id)
        assert loaded.expired is True

    def test_delete_missing(self, store):
        assert store.delete("kr_nonexistent") is False

    def test_list_records(self, store):
        for i in range(5):
            store.save(_make_record(confidence=i / 10 + 0.5))
        records = store.list_records(limit=3)
        assert len(records) == 3

    def test_list_excludes_expired(self, store):
        rec = _make_record()
        store.save(rec)
        store.delete(rec.id)
        records = store.list_records()
        assert len(records) == 0

    def test_list_includes_expired_when_asked(self, store):
        rec = _make_record()
        store.save(rec)
        store.delete(rec.id)
        records = store.list_records(include_expired=True)
        assert len(records) == 1

    def test_count(self, store):
        assert store.count() == 0
        store.save(_make_record())
        store.save(_make_record())
        assert store.count() == 2

    def test_list_by_domain(self, store):
        store.save(_make_record(domain="coding"))
        store.save(_make_record(domain="business"))
        store.save(_make_record(domain="coding"))
        records = store.list_records(domain="coding")
        assert len(records) == 2

    def test_list_by_type(self, store):
        store.save(_make_record(type=RecordType.HEURISTIC))
        store.save(_make_record(type=RecordType.CONSTRAINT))
        store.save(_make_record(type=RecordType.HEURISTIC))
        records = store.list_records(record_type="heuristic")
        assert len(records) == 2


class TestIndexes:
    def test_domain_index(self, store):
        store.save(_make_record(domain="coding"))
        store.save(_make_record(domain="business"))
        assert len(store.by_domain("coding")) == 1
        assert len(store.by_domain("business")) == 1
        assert len(store.by_domain("unknown")) == 0

    def test_tag_index(self, store):
        store.save(_make_record(tags=["api", "design"]))
        store.save(_make_record(tags=["api", "performance"]))
        assert len(store.by_tag("api")) == 2
        assert len(store.by_tag("design")) == 1
        assert len(store.by_tag("unknown")) == 0

    def test_anchor_index(self, store):
        store.save(_make_record(anchor=Anchor(file="src/main.py")))
        store.save(_make_record(anchor=Anchor(file="src/main.py")))
        store.save(_make_record(anchor=Anchor(file="src/utils.py")))
        assert len(store.by_anchor_file("src/main.py")) == 2
        assert len(store.by_anchor_file("src/utils.py")) == 1

    def test_rebuild_indexes(self, store):
        store.save(_make_record(domain="coding", tags=["api"]))
        store.save(_make_record(domain="business"))

        # Delete index files
        for f in store.index_dir.glob("*.json"):
            f.unlink()

        store.rebuild_indexes()
        assert len(store.by_domain("coding")) == 1
        assert len(store.by_tag("api")) == 1

    def test_update_removes_old_index_entries(self, store):
        rec = _make_record(domain="coding", tags=["old-tag"])
        store.save(rec)
        assert len(store.by_domain("coding")) == 1
        assert len(store.by_tag("old-tag")) == 1

        store.update(rec.id, {"domain": "business", "tags": ["new-tag"]})
        assert len(store.by_domain("coding")) == 0
        assert len(store.by_domain("business")) == 1
        assert len(store.by_tag("old-tag")) == 0
        assert len(store.by_tag("new-tag")) == 1

    def test_auto_rebuild_on_missing_index(self, store):
        """Indexes auto-rebuild if missing when read."""
        store.save(_make_record(domain="coding"))

        # Remove index
        (store.index_dir / "domain.json").unlink()

        # Should auto-rebuild
        assert len(store.by_domain("coding")) == 1

    def test_no_duplicate_index_entries(self, store):
        """Saving same record twice doesn't duplicate index entries."""
        rec = _make_record(domain="coding")
        store.save(rec)
        store.save(rec)  # save again
        assert len(store.by_domain("coding")) == 1

    def test_empty_domain_not_indexed(self, store):
        """Records with empty domain string are not indexed under domain."""
        store.save(_make_record(domain=""))
        idx = json.loads((store.index_dir / "domain.json").read_text())
        assert "" not in idx
