"""Tests for merge task creation and dependency resolution.

Task 1: complete_task with branch_name creates an atomic merge task.
Task 2: Merge tasks appear in ready queries and are not blocked.
"""

from __future__ import annotations

import pytest

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


# ---------------------------------------------------------------------------
# Task 1: complete_task + branch_name
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_complete_task_without_branch_returns_single_task(pool, project):
    """Calling complete_task without branch_name returns a single Task (backward compat)."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    result = await store.complete_task(pool, claimed.id, {"done": True})
    # Should be a single Task, not a tuple
    assert isinstance(result, Task)
    assert result.status == TaskStatus.DONE
    assert result.output == {"done": True}


@pytest.mark.asyncio
async def test_complete_task_with_branch_returns_tuple(pool, project):
    """Calling complete_task with branch_name returns (completed, merge_task)."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    result = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-abc123",
    )
    assert isinstance(result, tuple)
    completed, merge_task = result
    assert completed.status == TaskStatus.DONE
    assert merge_task.status == TaskStatus.PENDING
    assert merge_task.priority == "p0"


@pytest.mark.asyncio
async def test_merge_task_has_correct_context(pool, project):
    """The merge task context contains branch_name, parent_task_id, worktree_path."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-abc123",
    )
    ctx = merge_task.context
    assert ctx["branch_name"] == "worktree-abc123"
    assert ctx["parent_task_id"] == task.id
    assert ctx["worktree_path"] == ".claude/worktrees/abc123"


@pytest.mark.asyncio
async def test_merge_task_has_generated_id(pool, project):
    """The merge task gets a proper loom-{hex} ID."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-xyz789",
    )
    assert merge_task.id.startswith("loom-")
    assert len(merge_task.id) == 13  # "loom-" + 8 hex chars


@pytest.mark.asyncio
async def test_merge_task_title_contains_branch(pool, project):
    """The merge task title includes the branch name."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-feature42",
    )
    assert "worktree-feature42" in merge_task.title


@pytest.mark.asyncio
async def test_merge_task_persists_in_postgres(pool, project):
    """The merge task is actually saved to Postgres, not just returned."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-persist01",
    )
    # Re-fetch from Postgres
    fetched = await store.get_task(pool, merge_task.id)
    assert fetched.status == TaskStatus.PENDING
    assert fetched.context["branch_name"] == "worktree-persist01"


@pytest.mark.asyncio
async def test_complete_and_merge_are_atomic(pool, project):
    """If the impl task cannot be completed, no merge task is created."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.BLOCKED,
    )
    task = await store.create_task(pool, task)
    # Task is blocked (not claimed or pending), so completing should fail
    from loom.exceptions import TaskStateError
    with pytest.raises(TaskStateError):
        await store.complete_task(
            pool, task.id, {"done": True}, branch_name="worktree-atomic01",
        )
    # No merge tasks should exist
    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


# ---------------------------------------------------------------------------
# Task 2: Merge tasks in ready queries and dependency resolution
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_merge_task_appears_in_ready_tasks(pool, project):
    """Merge tasks (status=pending, no deps) show up in get_ready_tasks."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-ready01",
    )
    ready = await store.get_ready_tasks(pool, project)
    ready_ids = [t.id for t in ready]
    assert merge_task.id in ready_ids


@pytest.mark.asyncio
async def test_merge_task_not_blocked_by_deps(pool, project):
    """Merge tasks have no dependencies and are never in blocked state."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-nodeps01",
    )
    assert merge_task.depends_on == []
    assert merge_task.status == TaskStatus.PENDING


@pytest.mark.asyncio
async def test_merge_task_claimable(pool, project):
    """A merge task can be claimed by an agent (it is pending)."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-claim01",
    )
    merge_claimed = await store.claim_task(pool, merge_task.id, "merge-agent")
    assert merge_claimed.status == TaskStatus.CLAIMED
    assert merge_claimed.assignee == "merge-agent"


@pytest.mark.asyncio
async def test_merge_task_has_p0_priority_in_ready_query(pool, project):
    """Merge tasks are p0 and should appear before p1/p2 tasks in ready queries."""
    # Create a p2 regular task
    p2_task = Task(
        id=gen_task_id(), project_id=project, title="low priority task",
        status=TaskStatus.PENDING, priority="p2",
    )
    p2_task = await store.create_task(pool, p2_task)

    # Create an impl task and complete it with branch_name to get a p0 merge task
    impl = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    impl = await store.create_task(pool, impl)
    claimed = await store.claim_task(pool, impl.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-priority01",
    )

    ready = await store.get_ready_tasks(pool, project)
    ready_ids = [t.id for t in ready]
    # merge task (p0) should come before p2 task
    assert ready_ids.index(merge_task.id) < ready_ids.index(p2_task.id)


@pytest.mark.asyncio
async def test_ready_query_no_type_filter(pool, project):
    """Verify that get_ready_tasks does not filter by task type.

    This is an invariant check: the SQL only filters on status='pending'
    and unresolved dependencies, not on any task_type column or context field.
    """
    # Directly insert a task with merge-like context but no special type column
    merge_ctx_task = Task(
        id=gen_task_id(), project_id=project, title="manual merge-like task",
        status=TaskStatus.PENDING, priority="p1",
        context={"branch_name": "worktree-manual", "parent_task_id": "loom-fake0001"},
    )
    merge_ctx_task = await store.create_task(pool, merge_ctx_task)

    ready = await store.get_ready_tasks(pool, project)
    ready_ids = [t.id for t in ready]
    assert merge_ctx_task.id in ready_ids


@pytest.mark.asyncio
async def test_merge_task_same_project_as_parent(pool, project):
    """Merge task belongs to the same project as its parent task."""
    task = Task(
        id=gen_task_id(), project_id=project, title="impl task",
        status=TaskStatus.PENDING,
    )
    task = await store.create_task(pool, task)
    claimed = await store.claim_task(pool, task.id, "agent-1")
    _, merge_task = await store.complete_task(
        pool, claimed.id, {"done": True}, branch_name="worktree-proj01",
    )
    assert merge_task.project_id == project
