"""Tests for the SQLite database layer."""

import pytest

from agent_scout.db.database import Database
from agent_scout.db.models import (
    Memory,
    PriceSnapshot,
    Task,
    TaskStatus,
    TaskStep,
    ToolCall,
    TraceEntry,
)


@pytest.fixture
async def db(tmp_path):
    database = Database(str(tmp_path / "test.db"))
    await database.connect()
    yield database
    await database.close()


# --- Task CRUD ---


async def test_create_and_get_task(db: Database):
    task = Task(description="Find flights NYC to London")
    await db.create_task(task)

    retrieved = await db.get_task(task.id)
    assert retrieved is not None
    assert retrieved.description == "Find flights NYC to London"
    assert retrieved.status == TaskStatus.PENDING


async def test_update_task_status(db: Database):
    task = Task(description="Test task")
    await db.create_task(task)

    await db.update_task_status(task.id, TaskStatus.EXECUTING)
    retrieved = await db.get_task(task.id)
    assert retrieved is not None
    assert retrieved.status == TaskStatus.EXECUTING


async def test_update_task_with_result(db: Database):
    task = Task(description="Test task")
    await db.create_task(task)

    await db.update_task_status(task.id, TaskStatus.COMPLETED, result={"best_price": 450})
    retrieved = await db.get_task(task.id)
    assert retrieved is not None
    assert retrieved.status == TaskStatus.COMPLETED
    assert retrieved.result == {"best_price": 450}


async def test_update_task_with_error(db: Database):
    task = Task(description="Test task")
    await db.create_task(task)

    await db.update_task_status(task.id, TaskStatus.FAILED, error="API timeout")
    retrieved = await db.get_task(task.id)
    assert retrieved is not None
    assert retrieved.error == "API timeout"


async def test_list_tasks(db: Database):
    await db.create_task(Task(description="Task 1"))
    await db.create_task(Task(description="Task 2", status=TaskStatus.EXECUTING))
    await db.create_task(Task(description="Task 3"))

    all_tasks = await db.list_tasks()
    assert len(all_tasks) == 3

    pending = await db.list_tasks(status=TaskStatus.PENDING)
    assert len(pending) == 2

    executing = await db.list_tasks(status=TaskStatus.EXECUTING)
    assert len(executing) == 1


async def test_get_nonexistent_task(db: Database):
    result = await db.get_task("nonexistent")
    assert result is None


# --- Task Steps ---


async def test_create_and_get_steps(db: Database):
    task = Task(description="Test task")
    await db.create_task(task)

    step1 = TaskStep(task_id=task.id, step_number=1, thought="Searching for flights")
    step2 = TaskStep(
        task_id=task.id,
        step_number=2,
        thought="Comparing prices",
        action_type="tool_call",
        action_data={"tool": "web_search"},
    )
    await db.create_step(step1)
    await db.create_step(step2)

    steps = await db.get_steps(task.id)
    assert len(steps) == 2
    assert steps[0].step_number == 1
    assert steps[1].action_data == {"tool": "web_search"}


# --- Tool Calls ---


async def test_create_tool_call(db: Database):
    task = Task(description="Test task")
    await db.create_task(task)
    step = TaskStep(task_id=task.id, step_number=1)
    await db.create_step(step)

    tc = ToolCall(
        task_id=task.id,
        step_id=step.id,
        tool_name="web_search",
        tool_input={"query": "flights NYC London"},
        tool_output="Found 5 results",
        duration_ms=1200.5,
    )
    await db.create_tool_call(tc)

    # Verify via traces or direct query — tool_calls has no list method yet
    # but the insert shouldn't raise
    assert tc.id is not None


# --- Memories ---


async def test_store_and_search_memories(db: Database):
    m1 = Memory(
        content="User prefers morning flights",
        memory_type="preference",
        tags=["flights", "timing"],
    )
    m2 = Memory(
        content="Kayak usually has best prices for transatlantic",
        memory_type="strategy",
        tags=["flights", "source"],
    )
    await db.store_memory(m1)
    await db.store_memory(m2)

    all_memories = await db.search_memories()
    assert len(all_memories) == 2

    prefs = await db.search_memories(memory_type="preference")
    assert len(prefs) == 1
    assert prefs[0].content == "User prefers morning flights"

    tagged = await db.search_memories(tags=["source"])
    assert len(tagged) == 1
    assert "Kayak" in tagged[0].content


async def test_touch_memory(db: Database):
    m = Memory(content="test", memory_type="fact", tags=[])
    await db.store_memory(m)
    assert m.access_count == 0

    await db.touch_memory(m.id)
    memories = await db.search_memories()
    assert memories[0].access_count == 1


async def test_decay_memories(db: Database):
    m = Memory(content="test", memory_type="fact", tags=[], relevance_score=1.0)
    await db.store_memory(m)

    await db.decay_memories(decay_factor=0.5)
    memories = await db.search_memories()
    assert memories[0].relevance_score == pytest.approx(0.5)


# --- Traces ---


async def test_store_and_get_traces(db: Database):
    # Create real tasks so FK constraints are satisfied
    task1 = Task(id="t1", description="Task 1")
    task2 = Task(id="t2", description="Task 2")
    await db.create_task(task1)
    await db.create_task(task2)

    t1 = TraceEntry(task_id="t1", event_type="thought", data={"text": "thinking..."})
    t2 = TraceEntry(task_id="t1", event_type="action", data={"tool": "search"})
    t3 = TraceEntry(task_id="t2", event_type="thought", data={"text": "other task"})
    await db.store_trace(t1)
    await db.store_trace(t2)
    await db.store_trace(t3)

    all_traces = await db.get_traces()
    assert len(all_traces) == 3

    task_traces = await db.get_traces(task_id="t1")
    assert len(task_traces) == 2

    thought_traces = await db.get_traces(event_type="thought")
    assert len(thought_traces) == 2


# --- Price History ---


async def test_store_and_get_prices(db: Database):
    p1 = PriceSnapshot(
        item_name="NYC-LON flight",
        source="kayak",
        price=450.0,
        url="https://kayak.com/...",
    )
    p2 = PriceSnapshot(item_name="NYC-LON flight", source="google_flights", price=420.0)
    p3 = PriceSnapshot(item_name="Other item", source="amazon", price=29.99)
    await db.store_price(p1)
    await db.store_price(p2)
    await db.store_price(p3)

    history = await db.get_price_history("NYC-LON flight")
    assert len(history) == 2
    # Most recent first
    assert history[0].source in ("kayak", "google_flights")


# --- Migration ---


async def test_migration_runs_on_connect(tmp_path):
    """Verify that connecting to a new DB runs migrations automatically."""
    db = Database(str(tmp_path / "fresh.db"))
    await db.connect()

    cursor = await db.conn.execute("SELECT version FROM schema_migrations")
    rows = await cursor.fetchall()
    assert len(rows) >= 1
    assert rows[0]["version"] == 1

    await db.close()


async def test_reconnect_does_not_duplicate_migrations(tmp_path):
    """Verify migrations are idempotent."""
    db_path = str(tmp_path / "reuse.db")

    db1 = Database(db_path)
    await db1.connect()
    await db1.close()

    db2 = Database(db_path)
    await db2.connect()

    cursor = await db2.conn.execute("SELECT COUNT(*) FROM schema_migrations")
    row = await cursor.fetchone()
    assert row[0] == 1

    await db2.close()
