"""Tests for PersistentEvolutionStore - SQLite-backed evolution persistence"""

import os
import tempfile

import pytest

from agentkit.core.protocol import EvolutionEvent
from agentkit.evolution.evolution_store import (
    InMemoryEvolutionStore,
    PersistentEvolutionStore,
    create_evolution_store,
)


# ── Fixtures ──────────────────────────────────────────────


@pytest.fixture
def db_path(tmp_path):
    """Provide a temporary SQLite database path."""
    return str(tmp_path / "test_evolution.db")


@pytest.fixture
def store(db_path):
    """Create a PersistentEvolutionStore with a temporary database."""
    return PersistentEvolutionStore(db_path=db_path)


@pytest.fixture
def sample_event():
    """A sample EvolutionEvent."""
    return EvolutionEvent(
        agent_name="test_agent",
        change_type="prompt",
        before={"prompt": "old prompt"},
        after={"prompt": "new prompt"},
        metrics={"accuracy": 0.9},
    )


# ── record() + persistence tests ─────────────────────────


class TestRecordAndPersistence:
    async def test_record_returns_event_id(self, store, sample_event):
        event_id = await store.record(sample_event)
        assert event_id is not None
        assert isinstance(event_id, str)
        assert len(event_id) > 0

    async def test_record_sets_event_id_on_event(self, store, sample_event):
        assert sample_event.event_id is None
        await store.record(sample_event)
        assert sample_event.event_id is not None

    async def test_record_and_reopen_returns_event(self, db_path, sample_event):
        """Persistence test: record → close → reopen → list_events returns the event."""
        store1 = PersistentEvolutionStore(db_path=db_path)
        await store1.record(sample_event)
        event_id = sample_event.event_id
        del store1  # close

        store2 = PersistentEvolutionStore(db_path=db_path)
        events = await store2.list_events()
        assert len(events) == 1
        assert events[0]["id"] == event_id
        assert events[0]["agent_name"] == "test_agent"
        assert events[0]["change_type"] == "prompt"

    async def test_record_event_data_roundtrip(self, store, sample_event):
        """Verify before/after/metrics are stored and retrieved correctly."""
        await store.record(sample_event)
        events = await store.list_events()
        assert len(events) == 1
        e = events[0]
        assert e["before"] == {"prompt": "old prompt"}
        assert e["after"] == {"prompt": "new prompt"}
        assert e["metrics"] == {"accuracy": 0.9}
        assert e["status"] == "active"
        assert e["created_at"] is not None


# ── rollback() tests ──────────────────────────────────────


class TestRollback:
    async def test_rollback_success(self, store, sample_event):
        event_id = await store.record(sample_event)
        result = await store.rollback(event_id)
        assert result is True

        events = await store.list_events()
        assert len(events) == 1
        assert events[0]["status"] == "rolled_back"

    async def test_rollback_nonexistent_returns_false(self, store):
        result = await store.rollback("nonexistent-id")
        assert result is False

    async def test_rollback_persists_across_reopen(self, db_path, sample_event):
        """Rollback status persists after reopening the database."""
        store1 = PersistentEvolutionStore(db_path=db_path)
        event_id = await store1.record(sample_event)
        await store1.rollback(event_id)
        del store1

        store2 = PersistentEvolutionStore(db_path=db_path)
        events = await store2.list_events()
        assert events[0]["status"] == "rolled_back"


# ── list_events() tests ──────────────────────────────────


class TestListEvents:
    async def test_list_events_empty(self, store):
        events = await store.list_events()
        assert events == []

    async def test_list_events_filter_by_agent_name(self, store):
        event_a = EvolutionEvent(
            agent_name="agent_a", change_type="prompt", before={}, after={}
        )
        event_b = EvolutionEvent(
            agent_name="agent_b", change_type="prompt", before={}, after={}
        )
        await store.record(event_a)
        await store.record(event_b)

        events = await store.list_events(agent_name="agent_a")
        assert len(events) == 1
        assert events[0]["agent_name"] == "agent_a"

    async def test_list_events_filter_by_change_type(self, store):
        event_prompt = EvolutionEvent(
            agent_name="test", change_type="prompt", before={}, after={}
        )
        event_strategy = EvolutionEvent(
            agent_name="test", change_type="strategy", before={}, after={}
        )
        await store.record(event_prompt)
        await store.record(event_strategy)

        events = await store.list_events(change_type="strategy")
        assert len(events) == 1
        assert events[0]["change_type"] == "strategy"

    async def test_list_events_filter_by_status(self, store):
        event = EvolutionEvent(
            agent_name="test", change_type="prompt", before={}, after={}
        )
        event_id = await store.record(event)
        await store.rollback(event_id)

        active_events = await store.list_events(status="active")
        assert len(active_events) == 0

        rolled_back_events = await store.list_events(status="rolled_back")
        assert len(rolled_back_events) == 1
        assert rolled_back_events[0]["status"] == "rolled_back"

    async def test_list_events_multiple_with_combined_filters(self, store):
        """Integration: record multiple events, list with filters."""
        for i in range(3):
            event = EvolutionEvent(
                agent_name="agent_a" if i < 2 else "agent_b",
                change_type="prompt" if i % 2 == 0 else "strategy",
                before={},
                after={},
            )
            await store.record(event)

        # Filter by agent_name
        events = await store.list_events(agent_name="agent_a")
        assert len(events) == 2

        # Filter by change_type
        events = await store.list_events(change_type="strategy")
        assert len(events) == 1

        # Combined filter
        events = await store.list_events(agent_name="agent_a", change_type="prompt")
        assert len(events) == 1

    async def test_list_events_ordered_by_created_at_desc(self, store):
        """Events are returned newest first."""
        import asyncio

        event1 = EvolutionEvent(
            agent_name="test", change_type="prompt", before={"v": 1}, after={}
        )
        await store.record(event1)
        await asyncio.sleep(0.01)  # ensure different timestamps
        event2 = EvolutionEvent(
            agent_name="test", change_type="prompt", before={"v": 2}, after={}
        )
        await store.record(event2)

        events = await store.list_events()
        assert len(events) == 2
        # Newest first
        assert events[0]["before"]["v"] == 2
        assert events[1]["before"]["v"] == 1


# ── Skill version tests ──────────────────────────────────


class TestSkillVersions:
    async def test_record_and_list_skill_version(self, store):
        vid = await store.record_skill_version(
            skill_name="search",
            version="v1",
            content='{"prompt": "search for X"}',
        )
        assert vid is not None

        versions = await store.list_skill_versions("search")
        assert len(versions) == 1
        assert versions[0]["skill_name"] == "search"
        assert versions[0]["version"] == "v1"
        assert versions[0]["content"] == '{"prompt": "search for X"}'

    async def test_skill_version_with_parent(self, store):
        await store.record_skill_version("search", "v1", '{"prompt": "v1"}')
        await store.record_skill_version(
            "search", "v2", '{"prompt": "v2"}', parent_version="v1"
        )

        versions = await store.list_skill_versions("search")
        assert len(versions) == 2
        # Newest first
        assert versions[0]["version"] == "v2"
        assert versions[0]["parent_version"] == "v1"
        assert versions[1]["version"] == "v1"
        assert versions[1]["parent_version"] is None

    async def test_skill_versions_persist_across_reopen(self, db_path):
        store1 = PersistentEvolutionStore(db_path=db_path)
        await store1.record_skill_version("search", "v1", '{"prompt": "v1"}')
        del store1

        store2 = PersistentEvolutionStore(db_path=db_path)
        versions = await store2.list_skill_versions("search")
        assert len(versions) == 1
        assert versions[0]["version"] == "v1"

    async def test_list_skill_versions_empty(self, store):
        versions = await store.list_skill_versions("nonexistent")
        assert versions == []


# ── A/B test result tests ────────────────────────────────


class TestABTestResults:
    async def test_record_and_get_ab_test_result(self, store):
        rid = await store.record_ab_test_result(
            test_id="test_001", variant="control", score=0.85, sample_count=10
        )
        assert rid is not None

        results = await store.get_ab_test_results("test_001")
        assert len(results) == 1
        assert results[0]["test_id"] == "test_001"
        assert results[0]["variant"] == "control"
        assert results[0]["score"] == 0.85
        assert results[0]["sample_count"] == 10

    async def test_ab_test_multiple_variants(self, store):
        await store.record_ab_test_result("test_001", "control", 0.8, 10)
        await store.record_ab_test_result("test_001", "experiment", 0.9, 10)

        results = await store.get_ab_test_results("test_001")
        assert len(results) == 2

    async def test_ab_test_results_persist_across_reopen(self, db_path):
        store1 = PersistentEvolutionStore(db_path=db_path)
        await store1.record_ab_test_result("test_001", "control", 0.8, 5)
        del store1

        store2 = PersistentEvolutionStore(db_path=db_path)
        results = await store2.get_ab_test_results("test_001")
        assert len(results) == 1
        assert results[0]["variant"] == "control"

    async def test_get_ab_test_results_empty(self, store):
        results = await store.get_ab_test_results("nonexistent")
        assert results == []


# ── InMemoryEvolutionStore tests ─────────────────────────


class TestInMemoryEvolutionStore:
    async def test_record_and_list(self):
        store = InMemoryEvolutionStore()
        event = EvolutionEvent(
            agent_name="test", change_type="prompt", before={}, after={}
        )
        event_id = await store.record(event)
        assert event_id is not None

        events = await store.list_events()
        assert len(events) == 1
        assert events[0]["agent_name"] == "test"

    async def test_rollback(self):
        store = InMemoryEvolutionStore()
        event = EvolutionEvent(
            agent_name="test", change_type="prompt", before={}, after={}
        )
        event_id = await store.record(event)
        result = await store.rollback(event_id)
        assert result is True

        events = await store.list_events()
        assert events[0]["status"] == "rolled_back"

    async def test_rollback_nonexistent(self):
        store = InMemoryEvolutionStore()
        result = await store.rollback("nonexistent")
        assert result is False

    async def test_list_events_with_filters(self):
        store = InMemoryEvolutionStore()
        await store.record(
            EvolutionEvent(agent_name="a", change_type="prompt", before={}, after={})
        )
        await store.record(
            EvolutionEvent(agent_name="b", change_type="strategy", before={}, after={})
        )

        events = await store.list_events(agent_name="a")
        assert len(events) == 1

    async def test_skill_versions(self):
        store = InMemoryEvolutionStore()
        await store.record_skill_version("skill1", "v1", '{"data": 1}')
        versions = await store.list_skill_versions("skill1")
        assert len(versions) == 1
        assert versions[0]["version"] == "v1"

    async def test_ab_test_results(self):
        store = InMemoryEvolutionStore()
        await store.record_ab_test_result("t1", "control", 0.8, 5)
        results = await store.get_ab_test_results("t1")
        assert len(results) == 1
        assert results[0]["variant"] == "control"


# ── create_evolution_store factory tests ──────────────────


class TestCreateEvolutionStore:
    def test_create_memory_backend(self):
        store = create_evolution_store(backend="memory")
        assert isinstance(store, InMemoryEvolutionStore)

    def test_create_sqlite_backend(self, tmp_path):
        db_path = str(tmp_path / "factory_test.db")
        store = create_evolution_store(backend="sqlite", db_path=db_path)
        assert isinstance(store, PersistentEvolutionStore)

    def test_create_default_backend(self):
        store = create_evolution_store()
        assert isinstance(store, InMemoryEvolutionStore)

    def test_create_sql_backend_without_params_falls_back(self):
        """sql backend without session_factory/evolution_model falls back to memory."""
        store = create_evolution_store(backend="sql")
        assert isinstance(store, InMemoryEvolutionStore)
