"""Tests for loom.graph.query — pure unit tests and integration tests."""

from __future__ import annotations

import pytest

from loom.graph.query import (
    aggregate_progress,
    annotate_conflicts,
    build_conflict_map,
    format_mermaid,
    format_summary,
    tasks_to_graph_list,
    trace_blockers,
)
from loom.graph.task import Task


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

def _task(id: str, **kw) -> Task:
    defaults = {
        "project_id": "proj",
        "title": f"Task {id}",
        "status": "pending",
        "priority": "p1",
        "depends_on": [],
        "context": {},
    }
    defaults.update(kw)
    return Task(id=id, **defaults)


# ---------------------------------------------------------------------------
# Pure unit tests — tasks_to_graph_list
# ---------------------------------------------------------------------------

class TestTasksToGraphList:
    def test_converts_tasks(self):
        tasks = [
            _task("t1", assignee="agent-1", parent_id="root"),
            _task("t2", status="done", depends_on=["t1"]),
        ]
        result = tasks_to_graph_list(tasks)
        assert len(result) == 2
        assert result[0]["id"] == "t1"
        assert result[0]["status"] == "pending"
        assert result[0]["assignee"] == "agent-1"
        assert result[0]["parent_id"] == "root"
        assert result[0]["depends_on"] == []
        assert result[1]["status"] == "done"
        assert result[1]["depends_on"] == ["t1"]

    def test_empty_list(self):
        assert tasks_to_graph_list([]) == []


# ---------------------------------------------------------------------------
# Pure unit tests — format_mermaid
# ---------------------------------------------------------------------------

class TestFormatMermaid:
    def test_produces_diagram_with_deps(self):
        task_list = [
            {"id": "a", "title": "Alpha", "status": "pending", "depends_on": []},
            {"id": "b", "title": "Beta", "status": "pending", "depends_on": ["a"]},
        ]
        diagram = format_mermaid(task_list)
        assert diagram.startswith("graph TD")
        assert "a[Alpha]" in diagram
        assert "b[Beta]" in diagram
        assert "a --> b" in diagram

    def test_empty_list(self):
        assert format_mermaid([]) == "graph TD"


# ---------------------------------------------------------------------------
# Pure unit tests — format_summary
# ---------------------------------------------------------------------------

class TestFormatSummary:
    def test_counts_by_status(self):
        task_list = [
            {"id": "1", "status": "pending"},
            {"id": "2", "status": "pending"},
            {"id": "3", "status": "done"},
        ]
        summary = format_summary(task_list)
        assert "3 tasks total" in summary
        assert "pending: 2" in summary
        assert "done: 1" in summary

    def test_empty_list(self):
        summary = format_summary([])
        assert "0 tasks total" in summary


# ---------------------------------------------------------------------------
# Pure unit tests — build_conflict_map
# ---------------------------------------------------------------------------

class TestBuildConflictMap:
    def test_maps_files_to_task_ids(self):
        tasks = [
            _task("t1", context={"files": ["src/a.py", "src/b.py"]}),
            _task("t2", context={"files": ["src/b.py", "src/c.py"]}),
        ]
        result = build_conflict_map(tasks)
        assert result["src/a.py"] == ["t1"]
        assert sorted(result["src/b.py"]) == ["t1", "t2"]
        assert result["src/c.py"] == ["t2"]

    def test_no_files_returns_empty(self):
        tasks = [_task("t1"), _task("t2", context={"other": "data"})]
        assert build_conflict_map(tasks) == {}


# ---------------------------------------------------------------------------
# Pure unit tests — annotate_conflicts
# ---------------------------------------------------------------------------

class TestAnnotateConflicts:
    def test_adds_file_conflicts(self):
        dumps = [
            {"id": "t3", "context": {"files": ["src/b.py"]}},
        ]
        claimed_files = {"src/b.py": ["t1", "t2"]}
        annotate_conflicts(dumps, claimed_files)
        assert dumps[0]["file_conflicts"] == {"src/b.py": ["t1", "t2"]}

    def test_no_overlaps_leaves_unchanged(self):
        dumps = [
            {"id": "t3", "context": {"files": ["src/x.py"]}},
            {"id": "t4", "context": {}},
        ]
        claimed_files = {"src/b.py": ["t1"]}
        annotate_conflicts(dumps, claimed_files)
        assert "file_conflicts" not in dumps[0]
        assert "file_conflicts" not in dumps[1]


# ---------------------------------------------------------------------------
# Integration tests — trace_blockers
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_trace_blockers_no_deps(pool, project):
    from loom.graph.store import create_task

    task = _task("tb-1", project_id=project)
    await create_task(pool, task)

    result = await trace_blockers(pool, "tb-1")
    assert result["id"] == "tb-1"
    assert result["blockers"] == []


@pytest.mark.asyncio
async def test_trace_blockers_traces_chain(pool, project):
    from loom.graph.store import create_task

    a = _task("tb-a", project_id=project, title="Task A")
    b = _task("tb-b", project_id=project, title="Task B", depends_on=["tb-a"])
    await create_task(pool, a)
    await create_task(pool, b)

    result = await trace_blockers(pool, "tb-b")
    assert result["id"] == "tb-b"
    assert len(result["blockers"]) == 1
    assert result["blockers"][0]["id"] == "tb-a"
    assert result["blockers"][0]["status"] == "pending"


@pytest.mark.asyncio
async def test_trace_blockers_skips_done_deps(pool, project):
    from loom.graph.store import create_task, set_task_status
    from loom.graph.task import TaskStatus

    a = _task("tb-da", project_id=project)
    b = _task("tb-db", project_id=project, depends_on=["tb-da"])
    await create_task(pool, a)
    await create_task(pool, b)
    await set_task_status(pool, "tb-da", TaskStatus.DONE)

    result = await trace_blockers(pool, "tb-db")
    assert result["blockers"] == []


@pytest.mark.asyncio
async def test_trace_blockers_depth_limit(pool, project):
    """Cycles / deep chains hit the max_depth guard."""
    from loom.graph.store import create_task

    # Create a chain: c1 -> c2 -> c3 -> c4, trace from c4 with max_depth=2
    tasks = []
    for i in range(1, 5):
        deps = [f"tb-c{i - 1}"] if i > 1 else []
        tasks.append(_task(f"tb-c{i}", project_id=project, depends_on=deps))
    for t in tasks:
        await create_task(pool, t)

    result = await trace_blockers(pool, "tb-c4", max_depth=2)
    # Should still return a result without infinite recursion
    assert result["id"] == "tb-c4"


# ---------------------------------------------------------------------------
# Integration tests — aggregate_progress
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_aggregate_progress_no_claimed(pool, redis_conn, project):
    result = await aggregate_progress(pool, redis_conn, project)
    assert result["claimed_count"] == 0
    assert result["reporting_progress"] == 0
    assert result["average_percent"] is None
    assert result["tasks"] == []


@pytest.mark.asyncio
async def test_aggregate_progress_with_claimed(pool, redis_conn, project):
    from loom.graph.cache import set_task_progress
    from loom.graph.store import claim_task, create_task

    t1 = _task("ap-1", project_id=project)
    t2 = _task("ap-2", project_id=project)
    await create_task(pool, t1)
    await create_task(pool, t2)
    await claim_task(pool, "ap-1", "agent-a")
    await claim_task(pool, "ap-2", "agent-b")

    await set_task_progress(redis_conn, project, "ap-1", progress="halfway", percent=50)
    await set_task_progress(redis_conn, project, "ap-2", progress="almost done", percent=80)

    result = await aggregate_progress(pool, redis_conn, project)
    assert result["claimed_count"] == 2
    assert result["reporting_progress"] == 2
    assert result["average_percent"] == 65  # round((50+80)/2)
    assert len(result["tasks"]) == 2
