"""Tests for store operations (no live embedding model needed).

Integration tests requiring real models live in tests/integration/test_pipeline_integration.py.
"""

import pytest

from lilbee.core.config import cfg
from lilbee.data.store import Store


@pytest.fixture(autouse=True)
def isolated_db(tmp_path):
    """Point store at a temp directory, clean up after."""
    from lilbee.app.services import reset_services

    original = cfg.lancedb_dir
    cfg.lancedb_dir = tmp_path / "lancedb_test"
    reset_services()
    yield
    reset_services()
    cfg.lancedb_dir = original


@pytest.fixture()
def store():
    """Create a Store instance bound to the isolated config."""
    return Store(cfg)


class TestStoreOperations:
    """Cover store paths that don't need a live backend."""

    def test_add_chunks_and_search_empty_table(self, store):
        """add_chunks with data + search on that table."""
        vec = [0.1] * 768
        count = store.add_chunks(
            [
                {
                    "source": "test.pdf",
                    "content_type": "pdf",
                    "chunk_type": "raw",
                    "page_start": 1,
                    "page_end": 1,
                    "line_start": 0,
                    "line_end": 0,
                    "chunk": "The oil capacity is 5 quarts.",
                    "chunk_index": 0,
                    "vector": vec,
                }
            ]
        )
        assert count == 1
        results = store.search(vec, top_k=1)
        assert len(results) == 1
        assert "5 quarts" in results[0].chunk

    def test_add_chunks_empty_returns_zero(self, store):
        assert store.add_chunks([]) == 0

    def test_search_empty_store(self, store):
        assert store.search([0.1] * 768) == []

    def test_search_filters_by_max_distance(self, store):
        vec = [0.1] * 768
        # Use a very different query vector to produce high distance
        far_vec = [-0.1] * 768
        store.add_chunks(
            [
                {
                    "source": "test.pdf",
                    "content_type": "pdf",
                    "chunk_type": "raw",
                    "page_start": 1,
                    "page_end": 1,
                    "line_start": 0,
                    "line_end": 0,
                    "chunk": "Relevant content.",
                    "chunk_index": 0,
                    "vector": vec,
                }
            ]
        )
        # Tight threshold filters out distant matches
        assert store.search(far_vec, max_distance=0.001) == []
        # Disabled filtering (0) returns everything
        assert len(store.search(far_vec, max_distance=0)) == 1
        # Generous threshold returns the match
        assert len(store.search(far_vec, max_distance=100.0)) == 1

    def test_delete_by_source(self, store):
        vec = [0.1] * 768
        store.add_chunks(
            [
                {
                    "source": "remove_me.txt",
                    "content_type": "text",
                    "chunk_type": "raw",
                    "page_start": 0,
                    "page_end": 0,
                    "line_start": 0,
                    "line_end": 0,
                    "chunk": "Content to remove.",
                    "chunk_index": 0,
                    "vector": vec,
                }
            ]
        )
        store.delete_by_source("remove_me.txt")
        results = store.search(vec, top_k=5)
        assert all(r.source != "remove_me.txt" for r in results)

    def test_delete_by_source_with_single_quote(self, store):
        vec = [0.1] * 768
        store.add_chunks(
            [
                {
                    "source": "it's_a_file.txt",
                    "content_type": "text",
                    "chunk_type": "raw",
                    "page_start": 0,
                    "page_end": 0,
                    "line_start": 0,
                    "line_end": 0,
                    "chunk": "Content with quote.",
                    "chunk_index": 0,
                    "vector": vec,
                }
            ]
        )
        store.delete_by_source("it's_a_file.txt")
        results = store.search(vec, top_k=5)
        assert all(r.source != "it's_a_file.txt" for r in results)

    def test_delete_by_source_no_table(self, store):
        # Should not raise on empty store
        store.delete_by_source("nonexistent.txt")

    def testsafe_delete_exception(self):
        """Cover safe_delete logging on failure."""
        from unittest.mock import MagicMock

        from lilbee.data.store import safe_delete

        mock_table = MagicMock()
        mock_table.delete.side_effect = RuntimeError("test error")
        # Should not raise
        safe_delete(mock_table, "bad predicate")

    def testensure_table_handles_already_exists(self):
        """ensure_table recovers when create_table raises ValueError."""
        from unittest import mock

        from lilbee.data.store import ensure_table

        s = Store(cfg)
        db = s.get_db()
        schema = s._chunks_schema()
        mock_table = mock.MagicMock()

        with (
            mock.patch.object(db, "create_table", side_effect=ValueError("already exists")),
            mock.patch.object(db, "open_table", return_value=mock_table),
        ):
            result = ensure_table(db, "chunks", schema)
            assert result is mock_table

    def test_add_chunks_wrong_dimension_raises(self, store):
        wrong_dim_vec = [0.1] * 100  # Wrong dimension
        with pytest.raises(ValueError, match="Vector dimension mismatch"):
            store.add_chunks(
                [
                    {
                        "source": "test.pdf",
                        "content_type": "pdf",
                        "chunk_type": "raw",
                        "page_start": 1,
                        "page_end": 1,
                        "line_start": 0,
                        "line_end": 0,
                        "chunk": "test",
                        "chunk_index": 0,
                        "vector": wrong_dim_vec,
                    }
                ]
            )


class TestGetChunksBySource:
    def test_returns_chunks_for_source(self, store):
        vec = [0.1] * 768
        store.add_chunks(
            [
                {
                    "source": "doc.txt",
                    "content_type": "text",
                    "chunk_type": "raw",
                    "page_start": 0,
                    "page_end": 0,
                    "line_start": 0,
                    "line_end": 0,
                    "chunk": "Hello world",
                    "chunk_index": 0,
                    "vector": vec,
                },
            ]
        )
        chunks = store.get_chunks_by_source("doc.txt")
        assert len(chunks) == 1
        assert chunks[0].chunk == "Hello world"

    def test_empty_store_returns_empty(self, store):
        assert store.get_chunks_by_source("nope.txt") == []

    def test_filters_by_source(self, store):
        vec = [0.1] * 768
        store.add_chunks(
            [
                {
                    "source": "a.txt",
                    "content_type": "text",
                    "chunk_type": "raw",
                    "page_start": 0,
                    "page_end": 0,
                    "line_start": 0,
                    "line_end": 0,
                    "chunk": "From A",
                    "chunk_index": 0,
                    "vector": vec,
                },
                {
                    "source": "b.txt",
                    "content_type": "text",
                    "chunk_type": "raw",
                    "page_start": 0,
                    "page_end": 0,
                    "line_start": 0,
                    "line_end": 0,
                    "chunk": "From B",
                    "chunk_index": 0,
                    "vector": vec,
                },
            ]
        )
        chunks = store.get_chunks_by_source("a.txt")
        assert len(chunks) == 1
        assert chunks[0].source == "a.txt"


class TestSourceTracking:
    def test_upsert_and_retrieve(self, store):
        store.upsert_source("test.pdf", "abc123", 10)
        assert any(s["filename"] == "test.pdf" for s in store.get_sources())

    def test_delete_source(self, store):
        store.upsert_source("to_delete.pdf", "xyz", 5)
        store.delete_source("to_delete.pdf")
        assert not any(s["filename"] == "to_delete.pdf" for s in store.get_sources())

    def test_upsert_source_with_single_quote(self, store):
        store.upsert_source("it's_a_file.pdf", "abc123", 10)
        sources = store.get_sources()
        assert any(s["filename"] == "it's_a_file.pdf" for s in sources)
        # Update should work too (tests the delete predicate in upsert)
        store.upsert_source("it's_a_file.pdf", "def456", 20)
        sources = store.get_sources()
        matching = [s for s in sources if s["filename"] == "it's_a_file.pdf"]
        assert len(matching) == 1
        assert matching[0]["chunk_count"] == 20

    def test_drop_all_clears_everything(self, store):
        store.upsert_source("drop_test.pdf", "hash", 3)
        store.drop_all()
        assert store.get_sources() == []
