"""Tests for code_symbols integration with semantic search.

Verifies that code symbols indexed by CodeDB can be searched via
SemanticSearch through the vec_code_symbols virtual table.
"""
import tempfile
import os
from unittest.mock import MagicMock

import pytest

from greymatter_core.db import SQLiteDatabase
from greymatter_core.embeddings import serialize_f32
from greymatter_core.semantic import SemanticSearch, _TABLE_CONFIG


@pytest.fixture
def shared_db_path():
    """Provide a temp file path shared by CodeDB and SQLiteDatabase."""
    with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
        path = f.name
    yield path
    os.unlink(path)


@pytest.fixture
def code_db(shared_db_path):
    """CodeDB pointing at shared temp file."""
    from greymatter_code.db import CodeDB
    db = CodeDB(db_path=shared_db_path)
    yield db
    db.close()


@pytest.fixture
def core_db(shared_db_path, code_db):
    """SQLiteDatabase pointing at same shared temp file.

    Created AFTER CodeDB so code_symbols + vec_code_symbols tables exist.
    """
    db = SQLiteDatabase(path=shared_db_path)
    yield db
    db.close()


@pytest.fixture
def mock_embedder():
    """Mock embedding provider returning deterministic 768-d vectors."""
    embedder = MagicMock()
    embedder.dimensions = 768

    def fake_embed(text):
        vec = [0.0] * 768
        if "parse" in text.lower():
            vec[0] = 1.0
        if "function" in text.lower():
            vec[1] = 0.8
        if "database" in text.lower():
            vec[2] = 1.0
        if "query" in text.lower():
            vec[2] = 0.9
            vec[3] = 0.5
        return vec

    embedder.embed = MagicMock(side_effect=fake_embed)
    embedder.embed_batch = MagicMock(
        side_effect=lambda texts: [fake_embed(t) for t in texts]
    )
    return embedder


def _insert_symbol(code_db, name="parse_config", qualified_name="utils::parse_config",
                   signature="def parse_config(path: str) -> dict",
                   summary="Parse a YAML configuration file"):
    """Insert a code symbol via CodeDB."""
    from greymatter_code.models import CodeSymbol
    sym = CodeSymbol(
        repo="greymatter",
        file_path="src/utils.py",
        language="python",
        kind="function",
        name=name,
        qualified_name=qualified_name,
        signature=signature,
        byte_start=0,
        byte_end=200,
        summary=summary,
    )
    code_db.insert_symbol(sym)
    return sym


# --- Config tests ---

def test_code_symbols_in_table_config():
    """code_symbols should be registered in TABLE_CONFIG."""
    assert "code_symbols" in _TABLE_CONFIG
    cfg = _TABLE_CONFIG["code_symbols"]
    assert cfg["vec_table"] == "vec_code_symbols"
    assert cfg["pk_col"] == "symbol_id"
    assert cfg["source_table"] == "code_symbols"
    assert cfg["source_pk"] == "id"
    assert cfg["chunked"] is False
    assert callable(cfg["text_fn"])


def test_code_symbols_text_fn_combines_fields():
    """text_fn should combine qualified_name, signature, and summary."""
    cfg = _TABLE_CONFIG["code_symbols"]
    row = {
        "qualified_name": "utils::parse_config",
        "signature": "def parse_config(path: str) -> dict",
        "summary": "Parse a YAML configuration file",
    }
    text = cfg["text_fn"](row)
    assert "utils::parse_config" in text
    assert "def parse_config" in text
    assert "Parse a YAML" in text


def test_code_symbols_text_fn_handles_missing_fields():
    """text_fn should handle missing/None fields gracefully."""
    cfg = _TABLE_CONFIG["code_symbols"]
    row = {"qualified_name": "foo::bar", "signature": "", "summary": None}
    text = cfg["text_fn"](row)
    assert "foo::bar" in text


# --- Schema tests ---

def test_vec_code_symbols_table_created_by_code_db(code_db, core_db):
    """vec_code_symbols virtual table should exist in the shared DB."""
    row = core_db.execute_one(
        "SELECT name FROM sqlite_master WHERE type='table' AND name='vec_code_symbols'"
    )
    assert row is not None
    assert row["name"] == "vec_code_symbols"


# --- Indexing tests ---

def test_index_table_indexes_code_symbols(code_db, core_db, mock_embedder):
    """index_table('code_symbols') should embed all unindexed symbols."""
    _insert_symbol(code_db)
    search = SemanticSearch(core_db, mock_embedder)
    count = search.index_table("code_symbols")
    assert count == 1


def test_index_table_skips_already_indexed(code_db, core_db, mock_embedder):
    """index_table should not re-index symbols that already have embeddings."""
    _insert_symbol(code_db)
    search = SemanticSearch(core_db, mock_embedder)
    first = search.index_table("code_symbols")
    second = search.index_table("code_symbols")
    assert first == 1
    assert second == 0


def test_index_table_no_chunks_for_code_symbols(code_db, core_db, mock_embedder):
    """code_symbols should not produce chunks (chunked=False)."""
    _insert_symbol(code_db)
    search = SemanticSearch(core_db, mock_embedder)
    search.index_table("code_symbols")
    chunks = core_db.execute(
        "SELECT * FROM embedding_chunks WHERE source_table='code_symbols'"
    )
    assert len(chunks) == 0


# --- Search tests ---

def test_search_code_symbols_by_name(code_db, core_db, mock_embedder):
    """Searching for a function name should return matching code symbols."""
    _insert_symbol(code_db)
    search = SemanticSearch(core_db, mock_embedder)
    search.index_table("code_symbols")
    results = search.search("parse function", tables=["code_symbols"], limit=5)
    assert len(results) >= 1
    assert results[0]["source_type"] == "code_symbols"
    assert results[0]["name"] == "parse_config"


def test_search_code_symbols_returns_source_id(code_db, core_db, mock_embedder):
    """Search results should include source_id matching the symbol's primary key."""
    sym = _insert_symbol(code_db)
    search = SemanticSearch(core_db, mock_embedder)
    search.index_table("code_symbols")
    results = search.search("parse function", tables=["code_symbols"], limit=5)
    assert len(results) >= 1
    assert results[0]["source_id"] == sym.id


def test_search_ranks_by_distance(code_db, core_db, mock_embedder):
    """Closer semantic match should rank higher."""
    _insert_symbol(code_db, name="parse_config",
                   qualified_name="utils::parse_config",
                   signature="def parse_config(path: str) -> dict",
                   summary="Parse configuration file")
    _insert_symbol(code_db, name="run_query",
                   qualified_name="db::run_query",
                   signature="def run_query(sql: str) -> list",
                   summary="Execute a database query")
    search = SemanticSearch(core_db, mock_embedder)
    search.index_table("code_symbols")

    results = search.search("database query execution", tables=["code_symbols"], limit=5)
    assert len(results) == 2
    # run_query should rank higher for "database query" due to mock embedder
    assert results[0]["name"] == "run_query"


# --- Cross-table search tests ---

def test_cross_table_search_includes_code_symbols(code_db, core_db, mock_embedder):
    """Searching all tables should include code_symbols results."""
    _insert_symbol(code_db)
    # Also insert a knowledge entry
    core_db.execute(
        "INSERT INTO knowledge_entries (id, entry_type, title, content) VALUES (?, ?, ?, ?)",
        ("k1", "expertise", "Config Parsing", "How to parse configuration files"),
    )
    search = SemanticSearch(core_db, mock_embedder)
    search.index_table("code_symbols")
    search.index_table("knowledge")

    results = search.search("parse config", limit=10)
    source_types = {r["source_type"] for r in results}
    assert "code_symbols" in source_types
    assert "knowledge" in source_types


# --- Status tests ---

def test_status_includes_code_symbols(code_db, core_db, mock_embedder):
    """status() should report code_symbols in the document layer."""
    search = SemanticSearch(core_db, mock_embedder)
    status = search.status()
    assert "code_symbols" in status["document_layer"]
    assert status["document_layer"]["code_symbols"] == 0


def test_status_counts_indexed_symbols(code_db, core_db, mock_embedder):
    """status() should reflect indexed symbol count."""
    _insert_symbol(code_db)
    search = SemanticSearch(core_db, mock_embedder)
    search.index_table("code_symbols")
    status = search.status()
    assert status["document_layer"]["code_symbols"] == 1
