"""Tests for done-with-commit: commit SHA injection and idea completion."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from loom.exceptions import TaskStateError
from loom.git import get_head_sha
from loom.graph import store
from loom.graph.task import Priority, Task, TaskStatus
from loom.ids import task_id as gen_task_id


# ---------------------------------------------------------------------------
# Unit: get_head_sha
# ---------------------------------------------------------------------------


class TestGetHeadSha:
    def test_returns_sha_in_git_repo(self, tmp_path):
        """In a real git repo, get_head_sha returns a 40-char hex string."""
        import subprocess

        subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True)
        subprocess.run(["git", "config", "user.email", "test@test.com"], cwd=tmp_path, capture_output=True)
        subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, capture_output=True)
        (tmp_path / "f.txt").write_text("hello")
        subprocess.run(["git", "add", "."], cwd=tmp_path, capture_output=True)
        subprocess.run(["git", "commit", "-m", "init"], cwd=tmp_path, capture_output=True)

        sha = get_head_sha(str(tmp_path))
        assert sha is not None
        assert len(sha) == 40
        assert all(c in "0123456789abcdef" for c in sha)

    def test_returns_none_outside_git(self, tmp_path):
        """Outside a git repo, get_head_sha returns None."""
        sha = get_head_sha(str(tmp_path))
        assert sha is None

    def test_returns_none_on_bad_path(self):
        """Non-existent path returns None."""
        sha = get_head_sha("/nonexistent/path/that/does/not/exist")
        assert sha is None


# ---------------------------------------------------------------------------
# Integration: complete_idea in store.py
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
class TestCompleteIdea:
    async def test_idea_to_done(self, pool, project):
        """An idea can be completed directly → done with output."""
        idea = await store.create_idea(pool, project, "Ship dark mode")
        assert idea.status == TaskStatus.IDEA

        output = {"commit_sha": "abc123", "notes": "done inline"}
        done = await store.complete_idea(pool, idea.id, output)

        assert done.status == TaskStatus.DONE
        assert done.output["commit_sha"] == "abc123"
        assert done.done_at is not None

    async def test_complete_non_idea_raises(self, pool, project):
        """Completing a pending task via complete_idea raises TaskStateError."""
        task = Task(
            id=gen_task_id(), project_id=project, title="Regular task",
            status=TaskStatus.PENDING, priority=Priority.P1,
        )
        task = await store.create_task(pool, task)

        with pytest.raises(TaskStateError):
            await store.complete_idea(pool, task.id, {"result": "nope"})

    async def test_complete_idea_not_found(self, pool):
        """Completing a nonexistent idea raises TaskNotFoundError."""
        from loom.exceptions import TaskNotFoundError

        with pytest.raises(TaskNotFoundError):
            await store.complete_idea(pool, "nonexistent-id", {})


# ---------------------------------------------------------------------------
# Integration: loom_done with commit SHA injection (via store layer)
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
class TestDoneWithCommitSha:
    async def test_done_injects_commit_sha(self, pool, project):
        """When get_head_sha returns a SHA, output includes commit_sha."""
        task = Task(
            id=gen_task_id(), project_id=project, title="Add feature",
            status=TaskStatus.PENDING, priority=Priority.P1,
        )
        task = await store.create_task(pool, task)
        claimed = await store.claim_task(pool, task.id, "agent-1")

        # Simulate what loom_done does: inject SHA then complete
        output = {"summary": "feature added"}
        sha = "a" * 40
        output["commit_sha"] = sha

        done = await store.complete_task(pool, claimed.id, output)
        assert done.output["commit_sha"] == sha
        assert done.output["summary"] == "feature added"

    async def test_commit_sha_none_outside_git(self, tmp_path):
        """When not in a git repo, no commit_sha is injected."""
        output = {"summary": "done"}
        sha = get_head_sha(str(tmp_path))
        if sha:
            output["commit_sha"] = sha

        assert "commit_sha" not in output

    async def test_done_idea_directly(self, pool, project):
        """An idea can be completed via the loom_done flow (idea → done)."""
        idea = await store.create_idea(pool, project, "Quick fix idea")

        output = {"notes": "shipped it"}
        done = await store.complete_idea(pool, idea.id, output)

        assert done.status == TaskStatus.DONE
        assert done.output["notes"] == "shipped it"

    async def test_done_idea_with_commit(self, pool, project):
        """Completing an idea includes commit_sha when available."""
        idea = await store.create_idea(pool, project, "Add logging")

        output = {"notes": "added structured logging"}
        sha = "b" * 40
        output["commit_sha"] = sha

        done = await store.complete_idea(pool, idea.id, output)
        assert done.status == TaskStatus.DONE
        assert done.output["commit_sha"] == sha

    async def test_batch_done_injects_commit(self, pool, project):
        """batch_done stores commit_sha in output when provided."""
        tasks = []
        for i in range(2):
            t = Task(
                id=gen_task_id(), project_id=project, title=f"Batch task {i}",
                status=TaskStatus.PENDING, priority=Priority.P1,
            )
            t = await store.create_task(pool, t)
            await store.claim_task(pool, t.id, "agent-1")
            tasks.append(t)

        # Simulate batch_done with SHA injection
        sha = "c" * 40
        output = {"commit_sha": sha, "batch": True}
        done_tasks, failures = await store.batch_done(pool, [t.id for t in tasks], output)

        assert len(done_tasks) == 2
        assert len(failures) == 0
        for dt in done_tasks:
            assert dt.output["commit_sha"] == sha
            assert dt.status == TaskStatus.DONE
