"""Tests for auto-pathcheck pipeline integration.

Covers extract_paths_from_dict() and fix_paths_in_place() — the new
functions added for decompose pipeline path correction without DB access.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from loom.pathcheck import extract_paths_from_dict, fix_paths_in_place


# ── extract_paths_from_dict ──────────────────────────────────────────


class TestExtractPathsFromDict:
    def test_basic_extraction(self):
        paths = extract_paths_from_dict(
            "Implement loom/graph/store.py changes", {}, None,
        )
        assert ("loom/graph/store.py", "title") in paths

    def test_context_files(self):
        paths = extract_paths_from_dict(
            "Task", {"files": ["loom/config.py", "loom/cli.py"]}, None,
        )
        path_strs = [p for p, _ in paths]
        assert "loom/config.py" in path_strs
        assert "loom/cli.py" in path_strs

    def test_done_when(self):
        paths = extract_paths_from_dict(
            "Task", {}, "File loom/mcp/tools.py has the new function",
        )
        assert ("loom/mcp/tools.py", "done_when") in paths

    def test_context_description(self):
        paths = extract_paths_from_dict(
            "Task", {"description": "Edit loom/bus/channels.py"}, None,
        )
        assert ("loom/bus/channels.py", "context.description") in paths

    def test_context_notes(self):
        paths = extract_paths_from_dict(
            "Task", {"notes": "See loom/scanner.py"}, None,
        )
        assert ("loom/scanner.py", "context.notes") in paths

    def test_empty_inputs(self):
        paths = extract_paths_from_dict("", {}, None)
        assert paths == []

    def test_deduplication(self):
        paths = extract_paths_from_dict(
            "Fix loom/config.py",
            {"description": "The file loom/config.py needs changes"},
            None,
        )
        path_strs = [p for p, _ in paths]
        assert path_strs.count("loom/config.py") == 1

    def test_delegates_same_as_extract_paths(self):
        """extract_paths_from_dict should produce same results as extract_paths."""
        from loom.graph.task import Task, TaskStatus, Priority
        from loom.ids import task_id as gen_task_id
        from loom.pathcheck import extract_paths

        task = Task(
            id=gen_task_id(),
            project_id="00000000-0000-0000-0000-000000000001",
            title="Fix loom/graph/store.py",
            status=TaskStatus.PENDING,
            priority=Priority.P1,
            context={"files": ["loom/config.py"], "description": "Edit loom/cli.py"},
            done_when="loom/mcp/tools.py updated",
        )
        from_task = extract_paths(task)
        from_dict = extract_paths_from_dict(
            task.title, task.context, task.done_when,
        )
        assert from_task == from_dict


# ── fix_paths_in_place ───────────────────────────────────────────────


class _FakeTaskDef:
    """Minimal stand-in for TaskDefinition with mutable attributes."""

    def __init__(self, title: str, context: dict, done_when: str | None = None):
        self.title = title
        self.context = context
        self.done_when = done_when


class TestFixPathsInPlace:
    @pytest.fixture()
    def project_dir(self, tmp_path: Path) -> Path:
        (tmp_path / "loom").mkdir()
        (tmp_path / "loom" / "graph").mkdir()
        (tmp_path / "loom" / "graph" / "store.py").write_text("# store")
        (tmp_path / "loom" / "graph" / "cache.py").write_text("# cache")
        (tmp_path / "loom" / "config.py").write_text("# config")
        (tmp_path / "loom" / "orchestration").mkdir()
        (tmp_path / "loom" / "orchestration" / "loop.py").write_text("# loop")
        (tmp_path / "loom" / "skills").mkdir()
        (tmp_path / "loom" / "skills" / "decomposer.py").write_text("# decomposer")
        return tmp_path

    def test_fixes_stale_context_files(self, project_dir: Path):
        td = _FakeTaskDef(
            title="Fix the store",
            context={"files": ["loom/store.py"]},
        )
        corrections = fix_paths_in_place([td], project_dir)
        assert len(corrections) >= 1
        assert td.context["files"] == ["loom/graph/store.py"]

    def test_fixes_stale_description(self, project_dir: Path):
        td = _FakeTaskDef(
            title="Fix things",
            context={"description": "Edit loom/orchestrator.py to add retry"},
        )
        corrections = fix_paths_in_place([td], project_dir)
        assert len(corrections) >= 1
        assert "loom/orchestration/loop.py" in td.context["description"]
        assert "loom/orchestrator.py" not in td.context["description"]

    def test_fixes_stale_done_when(self, project_dir: Path):
        td = _FakeTaskDef(
            title="Fix things",
            context={},
            done_when="loom/orchestrator.py has retry logic",
        )
        corrections = fix_paths_in_place([td], project_dir)
        assert len(corrections) >= 1
        assert "loom/orchestration/loop.py" in td.done_when
        assert "loom/orchestrator.py" not in td.done_when

    def test_valid_paths_unchanged(self, project_dir: Path):
        td = _FakeTaskDef(
            title="Fix things",
            context={"files": ["loom/config.py", "loom/graph/store.py"]},
        )
        corrections = fix_paths_in_place([td], project_dir)
        assert corrections == []
        assert td.context["files"] == ["loom/config.py", "loom/graph/store.py"]

    def test_no_match_leaves_unchanged(self, project_dir: Path):
        td = _FakeTaskDef(
            title="Fix things",
            context={"files": ["totally/nonexistent/file.rs"]},
        )
        corrections = fix_paths_in_place([td], project_dir)
        assert corrections == []
        assert td.context["files"] == ["totally/nonexistent/file.rs"]

    def test_multiple_tasks(self, project_dir: Path):
        td1 = _FakeTaskDef("Task A", {"files": ["loom/store.py"]})
        td2 = _FakeTaskDef("Task B", {"files": ["loom/config.py"]})
        td3 = _FakeTaskDef("Task C", {"files": ["loom/orchestrator.py"]})
        corrections = fix_paths_in_place([td1, td2, td3], project_dir)
        # td1 and td3 should have corrections; td2 is valid
        assert len(corrections) >= 2
        assert td2.context["files"] == ["loom/config.py"]

    def test_empty_task_list(self, project_dir: Path):
        corrections = fix_paths_in_place([], project_dir)
        assert corrections == []

    def test_invalid_project_root(self):
        td = _FakeTaskDef("Task", {"files": ["loom/config.py"]})
        corrections = fix_paths_in_place([td], "/nonexistent/path/xyz")
        assert corrections == []

    def test_correction_record_shape(self, project_dir: Path):
        td = _FakeTaskDef("Fix store", {"files": ["loom/store.py"]})
        corrections = fix_paths_in_place([td], project_dir)
        assert len(corrections) >= 1
        c = corrections[0]
        assert "task_title" in c
        assert "stale_path" in c
        assert "corrected_path" in c
        assert "confidence" in c
        assert "source" in c
        assert c["task_title"] == "Fix store"

    def test_fixes_notes_field(self, project_dir: Path):
        td = _FakeTaskDef(
            title="Fix things",
            context={"notes": "Check loom/orchestrator.py for details"},
        )
        corrections = fix_paths_in_place([td], project_dir)
        assert len(corrections) >= 1
        assert "loom/orchestration/loop.py" in td.context["notes"]
