"""End-to-end integration tests for the merge task lifecycle.

Exercises the full flow: create task -> claim -> complete with branch_name ->
merge task creation -> cache sync -> ready queue visibility.  All tests use
real Postgres + Redis via testcontainers (Docker must be running).
"""

from __future__ import annotations

import pytest

from loom.graph import cache, store
from loom.graph.task import Priority, Task, TaskStatus
from loom.ids import task_id as gen_task_id

pytestmark = pytest.mark.integration


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_task(project_id: str, **overrides) -> Task:
    """Build a pending Task with sensible defaults for testing."""
    defaults = {
        "id": gen_task_id(),
        "project_id": project_id,
        "title": "Implementation task",
        "status": TaskStatus.PENDING,
        "priority": Priority.P1,
    }
    defaults.update(overrides)
    return Task(**defaults)


# ---------------------------------------------------------------------------
# Test 1: Merge task created on complete with branch
# ---------------------------------------------------------------------------


async def test_merge_task_created_on_complete_with_branch(pool, redis_conn, project):
    """Create a task, claim it, complete with branch_name.

    Assert a merge task is returned as second element of the tuple.
    Verify merge task fields: title, status=pending, priority=p0,
    context has branch_name and parent_task_id.
    """
    task = _make_task(project)
    task = await store.create_task(pool, task)

    # Claim and complete with a branch name
    await store.claim_task(pool, task.id, "agent-1")
    result = await store.complete_task(
        pool, task.id, {"summary": "done"}, branch_name="worktree-agent-abc123",
    )

    # Result must be a tuple of (completed_task, merge_task)
    assert isinstance(result, tuple), "Expected (Task, Task) tuple when branch_name given"
    completed, merge_task = result

    # Completed task assertions
    assert completed.status == TaskStatus.DONE
    assert completed.id == task.id

    # Merge task assertions
    assert merge_task.status == TaskStatus.PENDING
    assert merge_task.priority == Priority.P0
    assert "worktree-agent-abc123" in merge_task.title
    assert merge_task.context["branch_name"] == "worktree-agent-abc123"
    assert merge_task.context["parent_task_id"] == task.id

    # Merge task must have a valid loom-{hex} ID
    assert merge_task.id.startswith("loom-")
    assert len(merge_task.id) == 13

    # Merge task must be persisted in Postgres
    from_pg = await store.get_task(pool, merge_task.id)
    assert from_pg.status == TaskStatus.PENDING
    assert from_pg.context["branch_name"] == "worktree-agent-abc123"


# ---------------------------------------------------------------------------
# Test 2: No merge task without branch
# ---------------------------------------------------------------------------


async def test_no_merge_task_without_branch(pool, redis_conn, project):
    """Create, claim, complete WITHOUT branch_name.

    Assert result is just a Task (not a tuple).  No merge task created.
    """
    task = _make_task(project)
    task = await store.create_task(pool, task)

    await store.claim_task(pool, task.id, "agent-1")
    result = await store.complete_task(pool, task.id, {"summary": "done"})

    # Should be a plain Task, not a tuple
    assert isinstance(result, Task), "Expected single Task when no branch_name"
    assert result.status == TaskStatus.DONE
    assert result.output == {"summary": "done"}

    # No merge tasks should exist in the database
    async with pool.acquire() as conn:
        count = await conn.fetchval(
            "SELECT COUNT(*) FROM tasks WHERE project_id = $1::uuid "
            "AND title LIKE 'Merge branch%'",
            project,
        )
    assert count == 0


# ---------------------------------------------------------------------------
# Test 3: Merge task becomes ready in cache
# ---------------------------------------------------------------------------


async def test_merge_task_becomes_ready(pool, redis_conn, project):
    """Complete a task with branch_name, sync the merge task to cache,
    add to ready queue.  Call get_ready_tasks() and assert the merge task
    appears.
    """
    task = _make_task(project)
    task = await store.create_task(pool, task)

    await store.claim_task(pool, task.id, "agent-1")
    completed, merge_task = await store.complete_task(
        pool, task.id, {"done": True}, branch_name="worktree-agent-ready01",
    )

    # Sync both tasks to Redis
    await cache.sync_task(redis_conn, completed)
    await cache.sync_task(redis_conn, merge_task)
    await cache.add_to_ready_queue(redis_conn, merge_task)

    # Merge task should now appear in the ready queue
    ready = await cache.get_ready_tasks(redis_conn, pool, project)
    ready_ids = [t.id for t in ready]
    assert merge_task.id in ready_ids, (
        f"Merge task {merge_task.id} not found in ready queue: {ready_ids}"
    )


# ---------------------------------------------------------------------------
# Test 4: Merge task context fields
# ---------------------------------------------------------------------------


async def test_merge_task_context_fields(pool, redis_conn, project):
    """Verify merge task context contains all expected fields:
    branch_name, parent_task_id, worktree_path (derived from branch_name).
    """
    task = _make_task(project)
    task = await store.create_task(pool, task)

    await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, task.id, {"done": True}, branch_name="worktree-agent-ctx42",
    )

    ctx = merge_task.context
    assert "branch_name" in ctx
    assert "parent_task_id" in ctx
    assert "worktree_path" in ctx

    # Values match expectations
    assert ctx["branch_name"] == "worktree-agent-ctx42"
    assert ctx["parent_task_id"] == task.id
    # worktree_path is derived by removing the "worktree-" prefix from branch_name
    assert ctx["worktree_path"] == ".claude/worktrees/agent-ctx42"

    # Re-fetch from Postgres and verify context survives round-trip
    fetched = await store.get_task(pool, merge_task.id)
    assert fetched.context == ctx


# ---------------------------------------------------------------------------
# Test 5: Complete task state transition
# ---------------------------------------------------------------------------


async def test_complete_task_state_transition(pool, redis_conn, project):
    """Verify the original task transitions from claimed -> done,
    and the merge task starts as pending.
    """
    task = _make_task(project)
    task = await store.create_task(pool, task)

    # Verify initial state
    assert task.status == TaskStatus.PENDING

    # Claim
    claimed = await store.claim_task(pool, task.id, "agent-1")
    assert claimed.status == TaskStatus.CLAIMED
    assert claimed.assignee == "agent-1"

    # Complete with branch_name
    completed, merge_task = await store.complete_task(
        pool, task.id, {"result": "ok"}, branch_name="worktree-agent-trans01",
    )

    # Original task: claimed -> done
    assert completed.status == TaskStatus.DONE
    assert completed.done_at is not None
    assert completed.output == {"result": "ok"}

    # Merge task: starts as pending (never claimed or done)
    assert merge_task.status == TaskStatus.PENDING
    assert merge_task.assignee is None
    assert merge_task.claimed_at is None
    assert merge_task.done_at is None

    # Verify states via direct Postgres reads
    orig_pg = await store.get_task(pool, task.id)
    merge_pg = await store.get_task(pool, merge_task.id)
    assert orig_pg.status == TaskStatus.DONE
    assert merge_pg.status == TaskStatus.PENDING


# ---------------------------------------------------------------------------
# Test 6: Merge task has p0 priority
# ---------------------------------------------------------------------------


async def test_merge_task_has_p0_priority(pool, redis_conn, project):
    """Merge tasks should always be p0 to run before other work.

    Create a p2 regular task alongside a merge task and verify the merge
    task sorts first in the ready queue.
    """
    # Create a low-priority regular task
    low_prio = _make_task(project, title="Low priority work", priority=Priority.P2)
    low_prio = await store.create_task(pool, low_prio)

    # Create an implementation task, claim, and complete with branch
    impl = _make_task(project, title="Impl for merge")
    impl = await store.create_task(pool, impl)
    await store.claim_task(pool, impl.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, impl.id, {"done": True}, branch_name="worktree-agent-prio01",
    )

    # Assert the merge task is p0
    assert merge_task.priority == Priority.P0

    # Sync to Redis and build ready queue
    await cache.sync_task(redis_conn, low_prio)
    await cache.add_to_ready_queue(redis_conn, low_prio)
    await cache.sync_task(redis_conn, merge_task)
    await cache.add_to_ready_queue(redis_conn, merge_task)

    # Verify ordering: p0 merge task should come before p2 regular task
    ready = await cache.get_ready_tasks(redis_conn, pool, project)
    ready_ids = [t.id for t in ready]
    assert merge_task.id in ready_ids
    assert low_prio.id in ready_ids
    merge_idx = ready_ids.index(merge_task.id)
    low_idx = ready_ids.index(low_prio.id)
    assert merge_idx < low_idx, (
        f"Merge task (p0) at index {merge_idx} should come before "
        f"low-priority task (p2) at index {low_idx}"
    )

    # Also verify via direct Postgres ready query
    pg_ready = await store.get_ready_tasks(pool, project)
    pg_ready_ids = [t.id for t in pg_ready]
    assert pg_ready_ids.index(merge_task.id) < pg_ready_ids.index(low_prio.id)
