"""Tests for hybrid search (FTS5 + vector RRF)."""

import tempfile
from pathlib import Path
from unittest.mock import MagicMock

import pytest

from tokennuke.storage.database import Database
from tokennuke.parser.symbols import Symbol
from tokennuke.embedder.embed import hybrid_search


@pytest.fixture
def populated_db():
    """Create a database with sample symbols for search testing."""
    with tempfile.TemporaryDirectory() as tmpdir:
        db = Database(Path(tmpdir) / 'test.db')
        symbols = [
            Symbol(
                id='api.py::authenticate_user#function',
                file='api.py', name='authenticate_user',
                qualified_name='authenticate_user', kind='function',
                language='python',
                signature='def authenticate_user(request: Request) -> Response',
                docstring='Authenticate a user with JWT token validation.',
                line=10, end_line=25, byte_offset=100, byte_length=300,
                content_hash='aaa',
            ),
            Symbol(
                id='middleware.py::auth_middleware#function',
                file='middleware.py', name='auth_middleware',
                qualified_name='auth_middleware', kind='function',
                language='python',
                signature='def auth_middleware(app: ASGIApp)',
                docstring='ASGI middleware for authentication and authorization.',
                line=1, end_line=15, byte_offset=0, byte_length=200,
                content_hash='bbb',
            ),
            Symbol(
                id='models.py::User#class',
                file='models.py', name='User',
                qualified_name='User', kind='class',
                language='python',
                signature='class User(BaseModel)',
                docstring='User model with email and hashed password.',
                line=1, end_line=20, byte_offset=0, byte_length=400,
                content_hash='ccc',
            ),
            Symbol(
                id='utils.py::parse_config#function',
                file='utils.py', name='parse_config',
                qualified_name='parse_config', kind='function',
                language='python',
                signature='def parse_config(path: str) -> dict',
                docstring='Parse YAML configuration file.',
                line=1, end_line=10, byte_offset=0, byte_length=150,
                content_hash='ddd',
            ),
        ]
        db.upsert_file('api.py', 'h1', 'python', 500, 'content', symbols[:1], {})
        db.upsert_file('middleware.py', 'h2', 'python', 300, 'content', symbols[1:2], {})
        db.upsert_file('models.py', 'h3', 'python', 400, 'content', symbols[2:3], {})
        db.upsert_file('utils.py', 'h4', 'python', 200, 'content', symbols[3:4], {})
        yield db
        db.close()


class TestFTSSearch:
    def test_search_exact_name(self, populated_db):
        results = populated_db.search_symbols_fts('authenticate_user')
        assert len(results) > 0
        assert results[0]['name'] == 'authenticate_user'

    def test_search_partial_name(self, populated_db):
        results = populated_db.search_symbols_fts('auth')
        assert len(results) > 0

    def test_search_docstring_content(self, populated_db):
        results = populated_db.search_symbols_fts('JWT token')
        assert len(results) > 0

    def test_search_with_kind_filter(self, populated_db):
        results = populated_db.search_symbols_fts('auth', kind='function')
        assert all(r['kind'] == 'function' for r in results)

    def test_search_no_results(self, populated_db):
        results = populated_db.search_symbols_fts('zzzznotfound')
        assert len(results) == 0


class TestHybridSearch:
    def test_fts_only_fallback(self, populated_db):
        """Hybrid search works without embedder (FTS-only mode)."""
        results = hybrid_search(populated_db, 'authenticate', embedder=None)
        assert len(results) > 0

    def test_hybrid_with_mock_embedder(self, populated_db):
        """Hybrid search with mocked vector search."""
        mock_embedder = MagicMock()
        mock_embedder.embed.return_value = [0.1] * 384

        results = hybrid_search(
            populated_db, 'authentication middleware',
            embedder=mock_embedder,
        )
        assert isinstance(results, list)

    def test_results_have_scores(self, populated_db):
        results = hybrid_search(populated_db, 'user', embedder=None)
        for r in results:
            assert 'score' in r
            assert r['score'] > 0

    def test_limit_respected(self, populated_db):
        results = hybrid_search(populated_db, 'auth', embedder=None, limit=1)
        assert len(results) <= 1
