"""Unit tests for SqliteConversationStore."""

from __future__ import annotations

from pathlib import Path

import pytest

from agentkit.chat.sqlite_conversation_store import SqliteConversationStore


@pytest.fixture
def db_path(tmp_path: Path) -> str:
    """Return a temporary database path."""
    return str(tmp_path / "test_conversations.db")


@pytest.fixture
async def store(db_path: str) -> SqliteConversationStore:
    """Create a SqliteConversationStore with a temporary database."""
    s = SqliteConversationStore(db_path=db_path)
    yield s
    await s._close_db()


# ---------------------------------------------------------------------------
# Basic CRUD
# ---------------------------------------------------------------------------


class TestBasicCRUD:
    async def test_create_conversation(self, store: SqliteConversationStore) -> None:
        conv = await store.get_or_create()
        assert conv.id
        assert conv.created_at is not None
        assert conv.updated_at is not None
        assert conv.messages == []

    async def test_create_conversation_with_id(self, store: SqliteConversationStore) -> None:
        conv = await store.get_or_create("my-conv-id")
        assert conv.id == "my-conv-id"

    async def test_get_or_create_returns_existing(self, store: SqliteConversationStore) -> None:
        await store.get_or_create("reuse-id")
        await store.add_message("reuse-id", "user", "hello")
        conv2 = await store.get_or_create("reuse-id")
        assert conv2.id == "reuse-id"
        # In-memory cache should have the message
        assert len(conv2.messages) == 1

    async def test_add_message(self, store: SqliteConversationStore) -> None:
        await store.get_or_create("msg-test")
        msg = await store.add_message("msg-test", "user", "Hello world")
        assert msg.role == "user"
        assert msg.content == "Hello world"
        assert msg.metadata == {}

    async def test_add_message_with_metadata(self, store: SqliteConversationStore) -> None:
        await store.get_or_create("meta-test")
        msg = await store.add_message("meta-test", "assistant", "Hi", {"key": "value"})
        assert msg.metadata == {"key": "value"}

    async def test_add_message_nonexistent_conversation_raises(
        self, store: SqliteConversationStore
    ) -> None:
        with pytest.raises(KeyError, match="not found"):
            await store.add_message("nonexistent", "user", "hello")

    async def test_get_history(self, store: SqliteConversationStore) -> None:
        await store.get_or_create("hist-test")
        await store.add_message("hist-test", "user", "msg1")
        await store.add_message("hist-test", "assistant", "msg2")
        await store.add_message("hist-test", "user", "msg3")
        history = await store.get_history("hist-test")
        assert len(history) == 3
        assert history[0].content == "msg1"
        assert history[1].content == "msg2"
        assert history[2].content == "msg3"

    async def test_get_history_with_limit(self, store: SqliteConversationStore) -> None:
        await store.get_or_create("limit-test")
        for i in range(10):
            await store.add_message("limit-test", "user", f"msg{i}")
        history = await store.get_history("limit-test", limit=3)
        assert len(history) == 3
        # Should return the last 3 messages
        assert history[0].content == "msg7"
        assert history[1].content == "msg8"
        assert history[2].content == "msg9"

    async def test_get_history_empty(self, store: SqliteConversationStore) -> None:
        history = await store.get_history("nonexistent")
        assert history == []


# ---------------------------------------------------------------------------
# Persistence
# ---------------------------------------------------------------------------


class TestPersistence:
    async def test_data_survives_store_recreation(self, db_path: str) -> None:
        """Create store, add data, create new store with same DB path, verify data survives."""
        store1 = SqliteConversationStore(db_path=db_path)
        await store1.get_or_create("persist-conv")
        await store1.add_message("persist-conv", "user", "persistent message")
        await store1._close_db()

        store2 = SqliteConversationStore(db_path=db_path)
        history = await store2.get_history("persist-conv")
        assert len(history) == 1
        assert history[0].content == "persistent message"
        assert history[0].role == "user"
        await store2._close_db()

    async def test_conversations_survive_recreation(self, db_path: str) -> None:
        store1 = SqliteConversationStore(db_path=db_path)
        await store1.get_or_create("conv-a")
        await store1.get_or_create("conv-b")
        await store1._close_db()

        store2 = SqliteConversationStore(db_path=db_path)
        convs = await store2.list_conversations()
        ids = {c.id for c in convs}
        assert "conv-a" in ids
        assert "conv-b" in ids
        await store2._close_db()


# ---------------------------------------------------------------------------
# list_conversations
# ---------------------------------------------------------------------------


class TestListConversations:
    async def test_list_conversations_ordering(self, store: SqliteConversationStore) -> None:
        """Most recently updated conversation should appear first."""
        await store.get_or_create("conv-first")
        await store.get_or_create("conv-second")
        # Update conv1 by adding a message
        await store.add_message("conv-first", "user", "update")
        convs = await store.list_conversations()
        assert len(convs) == 2
        # conv1 was updated more recently
        assert convs[0].id == "conv-first"

    async def test_list_conversations_limit(self, store: SqliteConversationStore) -> None:
        for i in range(5):
            await store.get_or_create(f"conv-{i}")
        convs = await store.list_conversations(limit=3)
        assert len(convs) == 3


# ---------------------------------------------------------------------------
# LRU cache eviction
# ---------------------------------------------------------------------------


class TestLRUCache:
    async def test_cache_eviction(self, db_path: str) -> None:
        """Cache should evict oldest entries when over limit (data still in SQLite)."""
        store = SqliteConversationStore(db_path=db_path, max_conversations=3)
        for i in range(5):
            await store.get_or_create(f"evict-{i}")
        # Cache should have at most 3 entries
        assert len(store._cache) <= 3
        # But all 5 conversations should be in SQLite
        convs = await store.list_conversations(limit=10)
        assert len(convs) == 5
        await store._close_db()


# ---------------------------------------------------------------------------
# restore_from_store (no-op)
# ---------------------------------------------------------------------------


class TestRestoreFromStore:
    async def test_restore_is_noop(self, store: SqliteConversationStore) -> None:
        """restore_from_store should be a no-op for SQLite store."""
        # Should not raise
        await store.restore_from_store()
