"""
SQLiteStore tests — in-memory SQLite, no Postgres required.
"""
import pytest
from dot_search.store.sqlite import SQLiteStore
from dot_search.store.protocol import ExactSearch, SubSearch


SQLITE_URL = "sqlite+aiosqlite://"


@pytest.fixture
async def store():
    s = SQLiteStore(db_url=SQLITE_URL)
    yield s
    await s.close()


@pytest.fixture
async def table_with_data(store):
    conn = await store._get_conn()
    await conn.execute("CREATE TABLE IF NOT EXISTS docs (id TEXT PRIMARY KEY, body TEXT)")
    await conn.execute("INSERT OR IGNORE INTO docs (id, body) VALUES ('d1', 'hello world'), ('d2', 'foo bar'), ('d3', 'baz qux')")
    await conn.commit()
    yield
    await conn.execute("DROP TABLE IF EXISTS docs")
    await conn.commit()


async def test_add_embedding_column(store, table_with_data):
    await store.add_embedding_column("docs", "ds_body_emb", dimension=4)
    conn = await store._get_conn()
    cursor = await conn.execute("PRAGMA table_info(docs)")
    rows = await cursor.fetchall()
    columns = {row[1] for row in rows}
    assert "ds_body_emb" in columns


async def test_write_and_read_embeddings(store, table_with_data):
    await store.add_embedding_column("docs", "ds_body_emb", dimension=4)
    rows = [
        ("d1", [1.0, 0.0, 0.0, 0.0]),
        ("d2", [0.0, 1.0, 0.0, 0.0]),
        ("d3", [0.0, 0.0, 1.0, 0.0]),
    ]
    await store.write_embeddings("docs", "ds_body_emb", "id", rows)

    conn = await store._get_conn()
    cursor = await conn.execute(
        "SELECT id FROM docs WHERE ds_body_emb IS NOT NULL"
    )
    fetched = await cursor.fetchall()
    ids = {r[0] for r in fetched}
    assert ids == {"d1", "d2", "d3"}


async def test_read_column_with_source(store, table_with_data):
    rows = await store.read_column("docs", "id", "body")
    assert len(rows) == 3
    assert all(isinstance(text, str) for _, text in rows)



async def test_search_returns_closest(store, table_with_data):
    await store.add_embedding_column("docs", "ds_body_emb", dimension=4)
    await store.write_embeddings(
        "docs", "ds_body_emb", "id",
        [
            ("d1", [1.0, 0.0, 0.0, 0.0]),
            ("d2", [0.0, 1.0, 0.0, 0.0]),
            ("d3", [0.0, 0.0, 1.0, 0.0]),
        ],
    )
    subsearches = [
        SubSearch(
            embedding_column="ds_body_emb",
            query_embedding=[1.0, 0.0, 0.0, 0.0],
            weight=1.0,
        )
    ]
    results_per_sub = await store.search(
        table="docs",
        pk_column="id",
        subsearches=subsearches,
        bm25searches=[],
        exact_searches=[],
        where=None,
        limit=3,
    )
    assert len(results_per_sub) == 1
    results = results_per_sub[0]
    assert results[0].id == "d1"


async def test_add_bm25_index_is_noop(store, table_with_data):
    # Should not raise
    await store.add_bm25_index("docs", ["body"])


async def test_exact_search_matches_substring(store, table_with_data):
    results_per_search = await store.search(
        table="docs",
        pk_column="id",
        subsearches=[],
        bm25searches=[],
        exact_searches=[ExactSearch(column="body", query="hello")],
        where=None,
        limit=10,
    )
    assert len(results_per_search) == 1
    results = results_per_search[0]
    assert len(results) == 1
    assert results[0].id == "d1"


async def test_exact_search_no_match_returns_empty(store, table_with_data):
    results_per_search = await store.search(
        table="docs",
        pk_column="id",
        subsearches=[],
        bm25searches=[],
        exact_searches=[ExactSearch(column="body", query="zzznomatch")],
        where=None,
        limit=10,
    )
    assert results_per_search[0] == []


async def test_exact_search_respects_where(store, table_with_data):
    # "hello world" matches d1, but filter excludes it
    results_per_search = await store.search(
        table="docs",
        pk_column="id",
        subsearches=[],
        bm25searches=[],
        exact_searches=[ExactSearch(column="body", query="hello")],
        where="id != 'd1'",
        limit=10,
    )
    assert results_per_search[0] == []


async def test_exact_search_multiple_columns_produce_separate_lists(store, table_with_data):
    results_per_search = await store.search(
        table="docs",
        pk_column="id",
        subsearches=[],
        bm25searches=[],
        exact_searches=[
            ExactSearch(column="body", query="hello"),
            ExactSearch(column="body", query="foo"),
        ],
        where=None,
        limit=10,
    )
    assert len(results_per_search) == 2
    assert results_per_search[0][0].id == "d1"
    assert results_per_search[1][0].id == "d2"


async def test_exact_search_scores_are_all_one(store, table_with_data):
    results_per_search = await store.search(
        table="docs",
        pk_column="id",
        subsearches=[],
        bm25searches=[],
        exact_searches=[ExactSearch(column="body", query="o")],  # matches d1 (hello world), d2 (foo bar)
        where=None,
        limit=10,
    )
    results = results_per_search[0]
    assert all(r.score == 1.0 for r in results)
