"""Tests for loom.mcp._decompose_prep — decompose pre-processing helpers."""

from __future__ import annotations

import uuid

import pytest

from loom.graph.store import claim_task, complete_task, create_task, set_task_status
from loom.graph.task import Task, TaskStatus
from loom.mcp._decompose_prep import (
    inject_dependency_hints,
    inject_existing_work_context,
    inject_granularity,
    resolve_epic_context,
    resolve_parent_epic_deps,
    validate_decompose,
)


# ---------------------------------------------------------------------------
# Pure unit tests: inject_granularity
# ---------------------------------------------------------------------------


class TestInjectGranularity:
    def test_none_granularity_returns_context_unchanged(self):
        assert inject_granularity("some context", None) == "some context"

    def test_coarse_appends_guidance(self):
        result = inject_granularity("base", "coarse")
        assert result is not None
        assert result.startswith("base")
        assert "GRANULARITY" in result
        assert "fewer, larger tasks" in result

    def test_fine_appends_guidance(self):
        result = inject_granularity("base", "fine")
        assert result is not None
        assert result.startswith("base")
        assert "GRANULARITY" in result
        assert "small, focused tasks" in result

    def test_unknown_granularity_returns_context_unchanged(self):
        assert inject_granularity("ctx", "mega") == "ctx"

    def test_none_context_with_valid_granularity(self):
        result = inject_granularity(None, "coarse")
        assert result is not None
        assert "GRANULARITY" in result


# ---------------------------------------------------------------------------
# Integration tests: validate_decompose
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_validate_decompose_no_project_id(pool):
    result = await validate_decompose(pool, "", "some-key", "anthropic")
    assert result is not None
    assert "No active project" in result["error"]


@pytest.mark.asyncio
async def test_validate_decompose_no_api_key(pool):
    result = await validate_decompose(pool, "some-id", "", "anthropic")
    assert result is not None
    assert "No API key" in result["error"]


@pytest.mark.asyncio
async def test_validate_decompose_project_not_found(pool):
    fake_id = str(uuid.uuid4())
    result = await validate_decompose(pool, fake_id, "key", "anthropic")
    assert result is not None
    assert "not found" in result["error"]


@pytest.mark.asyncio
async def test_validate_decompose_returns_none_when_valid(pool, project):
    result = await validate_decompose(pool, project, "key", "anthropic")
    assert result is None


# ---------------------------------------------------------------------------
# Integration tests: resolve_epic_context
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_resolve_epic_context_with_epic(pool, redis_conn, project):
    task = Task(
        id=f"epic-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="My Epic",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, task)
    await set_task_status(pool, task.id, TaskStatus.EPIC)

    result = await resolve_epic_context(
        pool, redis_conn, project, task.id,
        goal="", context=None, force_leaf=False,
    )
    assert result["parent_epic_id"] == task.id
    assert "Decompose: My Epic" in result["goal"]
    assert result["epic"] is not None


@pytest.mark.asyncio
async def test_resolve_epic_context_with_non_epic_raises(pool, redis_conn, project):
    task = Task(
        id=f"leaf-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Leaf Task",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, task)

    with pytest.raises(ValueError, match="not an epic"):
        await resolve_epic_context(
            pool, redis_conn, project, task.id,
            goal="", context=None, force_leaf=False,
        )


# ---------------------------------------------------------------------------
# Integration tests: inject_dependency_hints
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_inject_dependency_hints_with_after(pool, redis_conn, project):
    task = Task(
        id=f"dep-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Dependency Task",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, task)

    result = await inject_dependency_hints(
        pool, redis_conn, project,
        context="base context",
        dependency_hints={"after": [task.id]},
    )
    assert result is not None
    assert "DEPENDENCY CONTEXT" in result
    assert "Dependency Task" in result
    assert task.id in result


# ---------------------------------------------------------------------------
# Integration tests: resolve_parent_epic_deps
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_resolve_parent_epic_deps_none_when_no_epic_no_hints(pool, redis_conn, project):
    result = await resolve_parent_epic_deps(
        pool, redis_conn, project,
        epic_id=None, dependency_hints=None,
    )
    assert result is None


@pytest.mark.asyncio
async def test_resolve_parent_epic_deps_returns_deps_from_epic(pool, redis_conn, project):
    # Create a dependency task first
    dep_task = Task(
        id=f"dep-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Dep",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, dep_task)

    # Create epic that depends on dep_task
    epic = Task(
        id=f"epic-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Epic With Deps",
        status=TaskStatus.PENDING,
        depends_on=[dep_task.id],
    )
    await create_task(pool, epic)
    await set_task_status(pool, epic.id, TaskStatus.EPIC)

    result = await resolve_parent_epic_deps(
        pool, redis_conn, project,
        epic_id=epic.id, dependency_hints=None,
    )
    assert result is not None
    assert dep_task.id in result


# ---------------------------------------------------------------------------
# Integration tests: inject_existing_work_context
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_inject_existing_work_includes_done_tasks(pool, project):
    """Done tasks appear with [DONE] marker in injected context."""
    task = Task(
        id=f"done-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Already Built Feature",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, task)
    await claim_task(pool, task.id, "agent-1")
    await complete_task(pool, task.id, {"result": "ok"})

    result = await inject_existing_work_context(pool, project, None)
    assert result is not None
    assert "[DONE] Already Built Feature" in result


@pytest.mark.asyncio
async def test_inject_existing_work_includes_in_progress(pool, project):
    """Pending/blocked tasks appear with status marker."""
    task = Task(
        id=f"wip-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Work In Progress",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, task)

    result = await inject_existing_work_context(pool, project, None)
    assert result is not None
    assert "[PENDING] Work In Progress" in result


@pytest.mark.asyncio
async def test_inject_existing_work_excludes_ids(pool, project):
    """Tasks in exclude_ids are not included."""
    task = Task(
        id=f"excl-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Excluded Task",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, task)

    result = await inject_existing_work_context(
        pool, project, "base", exclude_ids={task.id},
    )
    # Should still have the base context
    assert result is not None
    assert "Excluded Task" not in result


@pytest.mark.asyncio
async def test_inject_existing_work_empty_project(pool, project):
    """Returns context unchanged when project has no tasks."""
    result = await inject_existing_work_context(pool, project, "original")
    assert result == "original"


@pytest.mark.asyncio
async def test_inject_existing_work_appends_to_existing_context(pool, project):
    """New section is appended to existing context."""
    task = Task(
        id=f"app-{uuid.uuid4().hex[:8]}",
        project_id=project,
        title="Some Task",
        status=TaskStatus.PENDING,
    )
    await create_task(pool, task)

    result = await inject_existing_work_context(pool, project, "my context")
    assert result is not None
    assert result.startswith("my context")
    assert "EXISTING WORK" in result
