"""End-to-end integration tests proving Phase 1 deliverable."""

from __future__ import annotations

import pytest

from loom.bus import channels
from loom.graph import cache, deps, store
from loom.graph.project import create_project, get_project_status
from loom.graph.task import PRIORITY_SCORES, Priority, Task, TaskStatus
from loom.ids import task_id


@pytest.fixture
async def project(pool):
    """Create a test project and return its ID as a string."""
    record = await create_project(pool, "test-project", "Integration test project")
    return str(record["id"])


# ── Test 1: Full Agent Workflow ──────────────────────────────────────────


async def test_full_agent_workflow(pool, redis_conn, project):
    """create → rebuild cache → loom_ready → claim → done → restart → verify."""
    # Create a task
    t = Task(
        id=task_id(), project_id=project, title="Implement auth module",
        priority=Priority.P0, context={"spec": "OAuth2 flow"},
        done_when="Auth endpoint returns JWT",
    )
    created = await store.create_task(pool, t)
    assert created.status == TaskStatus.PENDING

    # Rebuild cache (simulates MCP server startup)
    synced = await cache.rebuild_cache(redis_conn, pool, project)
    assert synced >= 1

    # loom_ready equivalent: get ready tasks from cache
    ready = await cache.get_ready_tasks(redis_conn, pool, project)
    assert len(ready) >= 1
    assert any(r.id == created.id for r in ready)

    # Claim the task
    claimed = await store.claim_task(pool, created.id, "agent-1")
    assert claimed.status == TaskStatus.CLAIMED
    assert claimed.assignee == "agent-1"
    await cache.sync_task(redis_conn, claimed)
    await cache.remove_from_ready_queue(redis_conn, project, claimed.id)

    # Verify it's no longer in the ready queue
    ready_after_claim = await cache.get_ready_tasks(redis_conn, pool, project)
    assert not any(r.id == created.id for r in ready_after_claim)

    # Complete the task
    output = {"jwt_endpoint": "/api/auth/token", "tests_passed": True}
    done = await store.complete_task(pool, created.id, output)
    assert done.status == TaskStatus.DONE
    assert done.output == output
    assert done.done_at is not None
    await cache.sync_task(redis_conn, done)

    # Verify Postgres state
    from_pg = await store.get_task(pool, created.id)
    assert from_pg.status == TaskStatus.DONE
    assert from_pg.output == output

    # RESTART SIMULATION: flush Redis and rebuild
    await redis_conn.flushdb()
    synced_after = await cache.rebuild_cache(redis_conn, pool, project)
    # The done task is not "active" so it won't be synced back
    assert synced_after == 0

    # But Postgres still has it
    still_there = await store.get_task(pool, created.id)
    assert still_there.status == TaskStatus.DONE
    assert still_there.output == output


# ── Test 2: Dependency Cascade ───────────────────────────────────────────


async def test_dependency_cascade(pool, redis_conn, project):
    """A, B independent; C depends on A+B. Complete A→C blocked, complete B→C ready."""
    # Create tasks A, B (no deps), C (depends on A + B)
    a = Task(id=task_id(), project_id=project, title="Task A", priority=Priority.P0)
    b = Task(id=task_id(), project_id=project, title="Task B", priority=Priority.P1)
    a = await store.create_task(pool, a)
    b = await store.create_task(pool, b)

    c_status = await deps.compute_initial_status(pool, [a.id, b.id])
    assert c_status == TaskStatus.BLOCKED

    c = Task(
        id=task_id(), project_id=project, title="Task C",
        priority=Priority.P0, depends_on=[a.id, b.id], status=c_status,
    )
    c = await store.create_task(pool, c)
    assert c.status == TaskStatus.BLOCKED

    # Rebuild cache
    await cache.rebuild_cache(redis_conn, pool, project)

    # Only A and B should be ready
    ready = await cache.get_ready_tasks(redis_conn, pool, project)
    ready_ids = {r.id for r in ready}
    assert a.id in ready_ids
    assert b.id in ready_ids
    assert c.id not in ready_ids

    # Claim + complete A
    a_claimed = await store.claim_task(pool, a.id, "agent-1")
    a_done = await store.complete_task(pool, a.id, {"result": "A done"})
    await cache.sync_task(redis_conn, a_done)
    await cache.remove_from_ready_queue(redis_conn, project, a.id)
    unblocked = await deps.check_and_unblock(pool, redis_conn, a.id, project)
    # C should NOT be unblocked yet (B still pending)
    assert c.id not in unblocked

    # Verify C is still blocked in Postgres
    c_after_a = await store.get_task(pool, c.id)
    assert c_after_a.status == TaskStatus.BLOCKED

    # Claim + complete B
    b_claimed = await store.claim_task(pool, b.id, "agent-2")
    b_done = await store.complete_task(pool, b.id, {"result": "B done"})
    await cache.sync_task(redis_conn, b_done)
    await cache.remove_from_ready_queue(redis_conn, project, b.id)
    unblocked = await deps.check_and_unblock(pool, redis_conn, b.id, project)
    # Now C should be unblocked
    assert c.id in unblocked

    # Verify C is pending and in the ready queue
    c_after_b = await store.get_task(pool, c.id)
    assert c_after_b.status == TaskStatus.PENDING

    ready_after = await cache.get_ready_tasks(redis_conn, pool, project)
    ready_ids_after = {r.id for r in ready_after}
    assert c.id in ready_ids_after


# ── Test 3: Project Status ───────────────────────────────────────────────


async def test_project_status(pool, redis_conn, project):
    """Verify project status counts are accurate."""
    # Create a mix of tasks
    t1 = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="T1", priority=Priority.P0,
    ))
    t2 = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="T2", priority=Priority.P1,
    ))
    t3 = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="T3",
        priority=Priority.P2, depends_on=[t1.id], status=TaskStatus.BLOCKED,
    ))

    # Claim and complete t1
    await store.claim_task(pool, t1.id, "agent-1")
    await store.complete_task(pool, t1.id, {"done": True})

    # Fail t2
    await store.fail_task(pool, t2.id, "something went wrong")

    status = await get_project_status(pool, project)
    assert status.total == 3
    assert status.done == 1
    assert status.failed == 1
    assert status.blocked == 1
    assert status.open_escalations == 1


# ── Test 4: Cycle Detection ─────────────────────────────────────────────


async def test_cycle_detection(pool, project):
    """Adding a dependency that creates a cycle should raise DependencyCycleError."""
    from loom.exceptions import DependencyCycleError

    a = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="Cycle A",
    ))
    b = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="Cycle B", depends_on=[a.id],
        status=TaskStatus.BLOCKED,
    ))

    # Trying to make A depend on B would create a cycle: A -> B -> A
    with pytest.raises(DependencyCycleError):
        await deps.detect_cycle(pool, a.id, [b.id])


# ── Test 5: Priority Ordering ───────────────────────────────────────────


async def test_ready_queue_priority_ordering(pool, redis_conn, project):
    """P0 tasks should come before P1, which come before P2."""
    p2 = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="Low priority", priority=Priority.P2,
    ))
    p1 = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="Medium priority", priority=Priority.P1,
    ))
    p0 = await store.create_task(pool, Task(
        id=task_id(), project_id=project, title="High priority", priority=Priority.P0,
    ))

    await cache.rebuild_cache(redis_conn, pool, project)
    ready = await cache.get_ready_tasks(redis_conn, pool, project)

    assert len(ready) == 3
    assert ready[0].id == p0.id
    assert ready[1].id == p1.id
    assert ready[2].id == p2.id


# ── Test 6: Event Recording ─────────────────────────────────────────────


async def test_event_recording(pool, project):
    """Events are recorded in Postgres audit log."""
    eid = await store.record_event(
        pool, project, "task.created", task_id=None, agent_id="test",
        payload={"test": True},
    )
    assert eid > 0

    async with pool.acquire() as conn:
        row = await conn.fetchrow("SELECT * FROM events WHERE id = $1", eid)
    assert row["event_type"] == "task.created"
    assert row["agent_id"] == "test"
