"""Tests for Tantivy index."""

from datetime import datetime

import pytest

from fast_resume.adapters.base import Session
from fast_resume.index import TantivyIndex
from fast_resume.query import Filter


@pytest.fixture
def index(temp_dir):
    """Create a TantivyIndex instance with temp directory."""
    return TantivyIndex(index_path=temp_dir / "tantivy_index")


@pytest.fixture
def sample_session():
    """Create a sample session for testing."""
    return Session(
        id="session-123",
        agent="claude",
        title="Test session",
        directory="/home/user/project",
        timestamp=datetime(2024, 1, 15, 10, 30, 0),
        content="Help me fix this bug in the login system",
        message_count=4,
        mtime=1705312200.0,
    )


@pytest.fixture
def sample_session_updated():
    """Create an updated version of the sample session."""
    return Session(
        id="session-123",  # Same ID
        agent="claude",
        title="Test session - updated",
        directory="/home/user/project",
        timestamp=datetime(2024, 1, 15, 10, 30, 0),
        content="Help me fix this bug in the login system - with more context added",
        message_count=6,
        mtime=1705312300.0,
    )


class TestTantivyIndex:
    """Tests for TantivyIndex."""

    def test_add_and_retrieve_sessions(self, index, sample_session):
        """Test adding and retrieving sessions."""
        index.add_sessions([sample_session])

        sessions = index.get_all_sessions()
        assert len(sessions) == 1
        assert sessions[0].id == "session-123"
        assert sessions[0].title == "Test session"
        assert sessions[0].agent == "claude"

    def test_add_sessions_empty_list(self, index):
        """Test adding empty list does nothing."""
        index.add_sessions([])
        assert index.get_session_count() == 0

    def test_delete_sessions(self, index, sample_session):
        """Test deleting sessions by ID."""
        index.add_sessions([sample_session])
        assert index.get_session_count() == 1

        index.delete_sessions(["session-123"])
        assert index.get_session_count() == 0

    def test_delete_sessions_empty_list(self, index, sample_session):
        """Test deleting empty list does nothing."""
        index.add_sessions([sample_session])
        index.delete_sessions([])
        assert index.get_session_count() == 1

    def test_update_sessions_replaces_existing(
        self, index, sample_session, sample_session_updated
    ):
        """Test that update_sessions replaces existing session without duplicates."""
        # Add initial session
        index.add_sessions([sample_session])
        assert index.get_session_count() == 1

        # Update the session
        index.update_sessions([sample_session_updated])

        # Should still have only 1 session
        sessions = index.get_all_sessions()
        assert len(sessions) == 1
        assert sessions[0].id == "session-123"
        assert sessions[0].title == "Test session - updated"
        assert sessions[0].message_count == 6

    def test_update_sessions_no_duplicates_after_multiple_updates(
        self, index, sample_session
    ):
        """Test that multiple updates don't create duplicates."""
        index.add_sessions([sample_session])

        # Update multiple times
        for i in range(5):
            updated = Session(
                id="session-123",
                agent="claude",
                title=f"Update {i}",
                directory="/home/user/project",
                timestamp=datetime(2024, 1, 15, 10, 30, 0),
                content=f"Content version {i}",
                message_count=i + 1,
                mtime=1705312200.0 + i,
            )
            index.update_sessions([updated])

        # Should still have exactly 1 session
        sessions = index.get_all_sessions()
        assert len(sessions) == 1
        assert sessions[0].title == "Update 4"
        assert sessions[0].message_count == 5

    def test_update_sessions_adds_new_if_not_exists(self, index, sample_session):
        """Test that update_sessions adds session if it doesn't exist."""
        assert index.get_session_count() == 0

        index.update_sessions([sample_session])

        sessions = index.get_all_sessions()
        assert len(sessions) == 1
        assert sessions[0].id == "session-123"

    def test_update_sessions_empty_list(self, index, sample_session):
        """Test updating empty list does nothing."""
        index.add_sessions([sample_session])
        index.update_sessions([])
        assert index.get_session_count() == 1

    def test_update_multiple_sessions_atomically(self, index):
        """Test updating multiple sessions in a single call."""
        sessions = [
            Session(
                id=f"session-{i}",
                agent="claude",
                title=f"Session {i}",
                directory="/home/user/project",
                timestamp=datetime(2024, 1, 15, 10, 30, 0),
                content=f"Full content {i}",
                message_count=i,
                mtime=1705312200.0,
            )
            for i in range(3)
        ]

        # Add initial sessions
        index.add_sessions(sessions)
        assert index.get_session_count() == 3

        # Update all sessions at once
        updated_sessions = [
            Session(
                id=f"session-{i}",
                agent="claude",
                title=f"Updated Session {i}",
                directory="/home/user/project",
                timestamp=datetime(2024, 1, 15, 10, 30, 0),
                content=f"Updated full content {i}",
                message_count=i + 10,
                mtime=1705312300.0,
            )
            for i in range(3)
        ]
        index.update_sessions(updated_sessions)

        # Should still have exactly 3 sessions
        result = index.get_all_sessions()
        assert len(result) == 3

        # All should be updated
        for session in result:
            assert session.title.startswith("Updated Session")
            assert session.message_count >= 10

    def test_search_after_update(self, index, sample_session, sample_session_updated):
        """Test that search works correctly after updates."""
        index.add_sessions([sample_session])

        # Search for original content
        results = index.search("login")
        assert len(results) == 1

        # Update with new content
        index.update_sessions([sample_session_updated])

        # Search should still work and find the updated session
        results = index.search("context")
        assert len(results) == 1
        assert results[0][0] == "session-123"

    def test_search_with_hyphenated_agent_filter(self, index):
        """Test that agent filter works with hyphenated agent names like copilot-vscode.

        This is a regression test for a bug where hyphenated agent names
        were tokenized by the default tokenizer, splitting "copilot-vscode"
        into ["copilot", "vscode"] tokens. This caused term_query to fail
        when filtering by agent name during search.
        """
        sessions = [
            Session(
                id="session-vscode-1",
                agent="copilot-vscode",
                title="Code review feedback",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 10, 30, 0),
                content="Please review my code for bugs",
                message_count=2,
                mtime=1705312200.0,
            ),
            Session(
                id="session-claude-1",
                agent="claude",
                title="Code review session",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 11, 30, 0),
                content="Code review for authentication module",
                message_count=3,
                mtime=1705315800.0,
            ),
        ]
        index.add_sessions(sessions)

        # Search with query + hyphenated agent filter should work
        results = index.search(
            "review", agent_filter=Filter(include=["copilot-vscode"])
        )
        assert len(results) == 1
        assert results[0][0] == "session-vscode-1"

        # Verify other agent still works too
        results = index.search("review", agent_filter=Filter(include=["claude"]))
        assert len(results) == 1
        assert results[0][0] == "session-claude-1"

    def test_get_session_count(self, index):
        """Test getting session count."""
        assert index.get_session_count() == 0

        sessions = [
            Session(
                id=f"session-{i}",
                agent="claude",
                title=f"Session {i}",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 10, 30, 0),
                content="Full content",
                message_count=1,
                mtime=1705312200.0,
            )
            for i in range(5)
        ]
        index.add_sessions(sessions)

        assert index.get_session_count() == 5

    def test_get_session_count_with_agent_filter(self, index):
        """Test getting session count filtered by agent.

        This is a regression test for a bug where limit=0 was passed to
        Tantivy search, which panics because limit must be > 0.
        """
        sessions = [
            Session(
                id="session-claude-1",
                agent="claude",
                title="Claude session 1",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 10, 30, 0),
                content="Full content",
                message_count=1,
                mtime=1705312200.0,
            ),
            Session(
                id="session-claude-2",
                agent="claude",
                title="Claude session 2",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 11, 30, 0),
                content="Full content",
                message_count=1,
                mtime=1705315800.0,
            ),
            Session(
                id="session-vscode-1",
                agent="copilot-vscode",
                title="VSCode session",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 12, 30, 0),
                content="Full content",
                message_count=1,
                mtime=1705319400.0,
            ),
        ]
        index.add_sessions(sessions)

        # Total count
        assert index.get_session_count() == 3

        # Filtered counts
        assert index.get_session_count(agent_filter="claude") == 2
        assert index.get_session_count(agent_filter="copilot-vscode") == 1
        assert index.get_session_count(agent_filter="nonexistent") == 0

    def test_search_exact_match_found_among_many_fuzzy_matches(self, index):
        """Test that exact matches are found even with many fuzzy matches.

        This is a regression test for a bug where fuzzy-only search would
        return hundreds of low-scoring matches, pushing exact matches outside
        the result limit. For example, searching "110" would match "10", "100",
        "1100", etc. with similar scores, and the actual "110" session would
        rank #533 out of 556 matches.

        The fix uses hybrid search: exact matches (boosted 5x) + fuzzy matches,
        ensuring exact matches rank first.
        """
        # Create many sessions with fuzzy-matching content (10, 100, 1100, etc.)
        fuzzy_sessions = [
            Session(
                id=f"session-fuzzy-{i}",
                agent="claude",
                title=f"Session {i}",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 10, 0, 0),
                # These all fuzzy-match "110": 10, 100, 1100, 1101, etc.
                content=f"Working with value {10 + i} and code {1100 + i}",
                message_count=2,
                mtime=1705312200.0 + i,
            )
            for i in range(150)
        ]

        # Add one session with exact "110" match
        exact_session = Session(
            id="session-exact-110",
            agent="claude",
            title="110 limit????????",
            directory="/project",
            timestamp=datetime(2024, 1, 15, 12, 0, 0),
            content="Why is there a 110 limit on this?",
            message_count=2,
            mtime=1705320000.0,
        )

        index.add_sessions(fuzzy_sessions + [exact_session])

        # Search for "110" with default limit - exact match must be in results
        results = index.search("110", limit=100)
        result_ids = [sid for sid, _ in results]

        assert "session-exact-110" in result_ids, (
            "Exact match for '110' not found in top 100 results. "
            f"Got {len(results)} results: {result_ids[:5]}..."
        )
        # Should rank in top 10 (not buried at position 100+)
        position = result_ids.index("session-exact-110")
        assert position < 10, f"Exact match ranked #{position + 1}, expected top 10"

    def test_search_typo_tolerance(self, index):
        """Test that search has typo tolerance via fuzzy matching."""
        sessions = [
            Session(
                id="session-1",
                agent="claude",
                title="Authentication system",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 10, 30, 0),
                content="Implementing authentication for the app",
                message_count=2,
                mtime=1705312200.0,
            ),
        ]
        index.add_sessions(sessions)

        # Search with typo "authentcation" (missing 'i') should still find it
        results = index.search("authentcation")
        assert len(results) == 1
        assert results[0][0] == "session-1"

    def test_empty_query_returns_results_sorted_by_date(self, index):
        """Test that empty query returns sessions sorted by timestamp (newest first)."""
        sessions = [
            Session(
                id="oldest",
                agent="claude",
                title="Oldest session",
                directory="/project",
                timestamp=datetime(2024, 1, 10, 10, 0, 0),
                content="This is the oldest session",
                message_count=2,
                mtime=1704880800.0,
            ),
            Session(
                id="newest",
                agent="claude",
                title="Newest session",
                directory="/project",
                timestamp=datetime(2024, 1, 20, 10, 0, 0),
                content="This is the newest session",
                message_count=2,
                mtime=1705744800.0,
            ),
            Session(
                id="middle",
                agent="vibe",
                title="Middle session",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 10, 0, 0),
                content="This is the middle session",
                message_count=2,
                mtime=1705312800.0,
            ),
        ]
        index.add_sessions(sessions)

        # Empty query should return all sessions sorted by timestamp desc
        results = index.search("")
        assert len(results) == 3
        assert results[0][0] == "newest"
        assert results[1][0] == "middle"
        assert results[2][0] == "oldest"

    def test_text_query_returns_results_sorted_by_relevance(self, index):
        """Test that text query returns sessions sorted by relevance, not date."""
        sessions = [
            Session(
                id="older-relevant",
                agent="claude",
                title="Python authentication implementation",
                directory="/project",
                timestamp=datetime(2024, 1, 10, 10, 0, 0),
                content="Python Python Python authentication",
                message_count=2,
                mtime=1704880800.0,
            ),
            Session(
                id="newer-less-relevant",
                agent="claude",
                title="General coding session",
                directory="/project",
                timestamp=datetime(2024, 1, 20, 10, 0, 0),
                content="Some Python code here",
                message_count=2,
                mtime=1705744800.0,
            ),
        ]
        index.add_sessions(sessions)

        # Text query should return results by relevance (more matches = higher score)
        results = index.search("Python")
        assert len(results) == 2
        # The older session has more "Python" mentions, so should rank higher
        assert results[0][0] == "older-relevant"
        assert results[1][0] == "newer-less-relevant"

    def test_filtered_query_without_text_sorted_by_date(self, index):
        """Test that filtered query without text returns sessions sorted by date."""
        sessions = [
            Session(
                id="claude-old",
                agent="claude",
                title="Old Claude session",
                directory="/project",
                timestamp=datetime(2024, 1, 10, 10, 0, 0),
                content="Claude session content",
                message_count=2,
                mtime=1704880800.0,
            ),
            Session(
                id="claude-new",
                agent="claude",
                title="New Claude session",
                directory="/project",
                timestamp=datetime(2024, 1, 20, 10, 0, 0),
                content="Claude session content",
                message_count=2,
                mtime=1705744800.0,
            ),
            Session(
                id="vibe-session",
                agent="vibe",
                title="Vibe session",
                directory="/project",
                timestamp=datetime(2024, 1, 15, 10, 0, 0),
                content="Vibe session content",
                message_count=2,
                mtime=1705312800.0,
            ),
        ]
        index.add_sessions(sessions)

        # Filter by agent with empty text should return sorted by date
        results = index.search("", agent_filter=Filter(include=["claude"]))
        assert len(results) == 2
        assert results[0][0] == "claude-new"
        assert results[1][0] == "claude-old"
