"""Tests for cleave reunify module - aggregation, merge, and review generation."""

from __future__ import annotations

from pathlib import Path

import pytest

from cleave.core.reunify import (
    aggregate_results,
    generate_merge_md,
    generate_review_md,
    reunify_workspace,
)
from cleave.core.conflicts import Conflict


class TestAggregateResults:
    """Tests for aggregating results from multiple task files."""

    @pytest.fixture
    def task_files(self, tmp_path: Path) -> list[Path]:
        """Create task files for testing."""
        files = []
        for i in range(3):
            task = tmp_path / f"{i}-task.md"
            files.append(task)
        return files

    def test_aggregates_success_statuses(self, task_files: list[Path]) -> None:
        """Should aggregate statuses from all tasks."""
        task_files[0].write_text("## Result\n\n**Status:** SUCCESS")
        task_files[1].write_text("## Result\n\n**Status:** SUCCESS")
        task_files[2].write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        assert result["rollup_status"] == "SUCCESS"
        assert result["statuses"]["SUCCESS"] == 3

    def test_rollup_failed_if_any_failed(self, task_files: list[Path]) -> None:
        """Should rollup to FAILED if any task failed."""
        task_files[0].write_text("## Result\n\n**Status:** SUCCESS")
        task_files[1].write_text("## Result\n\n**Status:** FAILED")
        task_files[2].write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        assert result["rollup_status"] == "FAILED"

    def test_rollup_partial_if_any_partial(self, task_files: list[Path]) -> None:
        """Should rollup to PARTIAL if any task partial (and none failed)."""
        task_files[0].write_text("## Result\n\n**Status:** SUCCESS")
        task_files[1].write_text("## Result\n\n**Status:** PARTIAL")
        task_files[2].write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        assert result["rollup_status"] == "PARTIAL"

    def test_rollup_pending_if_all_pending(self, task_files: list[Path]) -> None:
        """Should rollup to PENDING if all tasks pending."""
        task_files[0].write_text("## Result\n\n**Status:** PENDING")
        task_files[1].write_text("## Result\n\n**Status:** PENDING")
        task_files[2].write_text("## Result\n\n**Status:** PENDING")

        result = aggregate_results([str(f) for f in task_files])
        assert result["rollup_status"] == "PENDING"

    def test_aggregates_file_claims(self, task_files: list[Path]) -> None:
        """Should collect all file claims with deduplication."""
        task_files[0].write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/file1.py
- src/shared.py
""")
        task_files[1].write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/file2.py
- src/shared.py
""")
        task_files[2].write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        # Should have deduplicated shared.py
        assert "src/file1.py" in result["all_files"]
        assert "src/file2.py" in result["all_files"]
        # Count should reflect deduplication
        assert len(result["all_files"]) >= 2

    def test_aggregates_interfaces(self, task_files: list[Path]) -> None:
        """Should collect all interfaces with deduplication."""
        task_files[0].write_text("""
## Result

**Status:** SUCCESS

**Interfaces Published:**
- `login(user: str) -> Token`
""")
        task_files[1].write_text("""
## Result

**Status:** SUCCESS

**Interfaces Published:**
- `logout(token: str) -> bool`
""")
        task_files[2].write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        assert len(result["all_interfaces"]) >= 2

    def test_aggregates_decisions(self, task_files: list[Path]) -> None:
        """Should collect all decisions."""
        task_files[0].write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Used JWT for tokens
""")
        task_files[1].write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Chose PostgreSQL for storage
""")
        task_files[2].write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        assert len(result["all_decisions"]) >= 2

    def test_tracks_verification_summary(self, task_files: list[Path]) -> None:
        """Should track which tasks have verification evidence."""
        task_files[0].write_text("""
## Result

**Status:** SUCCESS

**Verification:**
- Test command: `pytest tests/`
- Test output: All passed
- Coverage: 85%
""")
        task_files[1].write_text("## Result\n\n**Status:** SUCCESS")
        task_files[2].write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        verified = [v for v in result["verification_summary"] if v["has_verification"]]
        assert len(verified) >= 1

    def test_by_task_contains_all_tasks(self, task_files: list[Path]) -> None:
        """Should have entry for each task in by_task."""
        for f in task_files:
            f.write_text("## Result\n\n**Status:** SUCCESS")

        result = aggregate_results([str(f) for f in task_files])
        assert len(result["by_task"]) == 3
        assert result["task_count"] == 3


class TestGenerateMergeMd:
    """Tests for merge.md generation."""

    def test_generates_merge_with_conflicts(self) -> None:
        """Should include conflict resolution sections."""
        conflicts = [
            Conflict(
                type="file_overlap",
                description="Both modified utils.py",
                involved=[0, 1],
                resolution="3way_merge",
            ),
        ]
        aggregated = {
            "task_count": 2,
            "rollup_status": "SUCCESS",
            "all_files": ["utils.py"],
            "all_interfaces": [],
            "all_decisions": [],
            "all_assumptions": [],
            "by_task": [
                {"id": 0, "status": "SUCCESS", "file_count": 1, "interface_count": 0},
                {"id": 1, "status": "SUCCESS", "file_count": 1, "interface_count": 0},
            ],
        }

        content = generate_merge_md(conflicts, aggregated, ".cleave")
        assert "Conflict 1" in content
        assert "file_overlap" in content
        assert "utils.py" in content
        assert "3way_merge" in content.lower() or "3-way" in content.lower() or "merge" in content.lower()

    def test_generates_merge_without_conflicts(self) -> None:
        """Should indicate no conflicts when none exist."""
        conflicts = []
        aggregated = {
            "task_count": 2,
            "rollup_status": "SUCCESS",
            "all_files": ["a.py", "b.py"],
            "all_interfaces": [],
            "all_decisions": [],
            "all_assumptions": [],
            "by_task": [
                {"id": 0, "status": "SUCCESS", "file_count": 1, "interface_count": 0},
                {"id": 1, "status": "SUCCESS", "file_count": 1, "interface_count": 0},
            ],
        }

        content = generate_merge_md(conflicts, aggregated, ".cleave")
        assert "No conflicts detected" in content

    def test_includes_task_status_table(self) -> None:
        """Should include task status table."""
        conflicts = []
        aggregated = {
            "task_count": 2,
            "rollup_status": "SUCCESS",
            "all_files": [],
            "all_interfaces": [],
            "all_decisions": [],
            "all_assumptions": [],
            "by_task": [
                {"id": 0, "status": "SUCCESS", "file_count": 1, "interface_count": 2},
                {"id": 1, "status": "PARTIAL", "file_count": 3, "interface_count": 0},
            ],
        }

        content = generate_merge_md(conflicts, aggregated, ".cleave")
        assert "| Task | Status |" in content or "Task" in content

    def test_includes_reunification_checklist(self) -> None:
        """Should include reunification checklist."""
        conflicts = []
        aggregated = {
            "task_count": 1,
            "rollup_status": "SUCCESS",
            "all_files": [],
            "all_interfaces": [],
            "all_decisions": [],
            "all_assumptions": [],
            "by_task": [{"id": 0, "status": "SUCCESS", "file_count": 0, "interface_count": 0}],
        }

        content = generate_merge_md(conflicts, aggregated, ".cleave")
        assert "Checklist" in content or "checklist" in content


class TestGenerateReviewMd:
    """Tests for adversarial review.md generation."""

    def test_generates_review_with_verification_summary(self) -> None:
        """Should include verification coverage summary."""
        aggregated = {
            "task_count": 2,
            "rollup_status": "SUCCESS",
            "all_files": [],
            "all_interfaces": ["login() -> Token"],
            "all_decisions": [],
            "all_assumptions": [],
            "by_task": [
                {"id": 0, "status": "SUCCESS", "summary": "Implemented auth", "alignment": {}},
                {"id": 1, "status": "SUCCESS", "summary": "Added routes", "alignment": {}},
            ],
            "verification_summary": [
                {"task_id": 0, "command": "pytest", "output": "passed", "coverage": "90%", "edge_cases": [], "has_verification": True},
                {"task_id": 1, "command": None, "output": None, "coverage": None, "edge_cases": [], "has_verification": False},
            ],
        }
        conflicts = []

        content = generate_review_md(aggregated, conflicts, ".cleave")
        assert "1/2" in content  # 1 of 2 tasks verified
        assert "pytest" in content

    def test_includes_integration_probes(self) -> None:
        """Should include integration probe checklist."""
        aggregated = {
            "task_count": 2,
            "rollup_status": "SUCCESS",
            "all_files": [],
            "all_interfaces": ["login(user: str) -> Token", "validate(token: str) -> bool"],
            "all_decisions": [],
            "all_assumptions": [],
            "by_task": [
                {"id": 0, "status": "SUCCESS", "summary": "Auth", "alignment": {}},
                {"id": 1, "status": "SUCCESS", "summary": "Validation", "alignment": {}},
            ],
            "verification_summary": [
                {"task_id": 0, "command": None, "output": None, "coverage": None, "edge_cases": [], "has_verification": False},
                {"task_id": 1, "command": None, "output": None, "coverage": None, "edge_cases": [], "has_verification": False},
            ],
        }
        conflicts = []

        content = generate_review_md(aggregated, conflicts, ".cleave")
        assert "login" in content
        assert "Integration" in content or "interface" in content.lower()

    def test_includes_gap_analysis(self) -> None:
        """Should include gap analysis section."""
        aggregated = {
            "task_count": 1,
            "rollup_status": "SUCCESS",
            "all_files": [],
            "all_interfaces": [],
            "all_decisions": ["Used JWT"],
            "all_assumptions": [],
            "by_task": [{"id": 0, "status": "SUCCESS", "summary": "Done", "alignment": {}}],
            "verification_summary": [
                {"task_id": 0, "command": None, "output": None, "coverage": None, "edge_cases": [], "has_verification": False}
            ],
        }
        conflicts = []

        content = generate_review_md(aggregated, conflicts, ".cleave")
        assert "Gap" in content or "gap" in content

    def test_includes_edge_cases_section(self) -> None:
        """Should include edge cases from verification."""
        aggregated = {
            "task_count": 1,
            "rollup_status": "SUCCESS",
            "all_files": [],
            "all_interfaces": [],
            "all_decisions": [],
            "all_assumptions": [],
            "by_task": [{"id": 0, "status": "SUCCESS", "summary": "Done", "alignment": {}}],
            "verification_summary": [
                {"task_id": 0, "command": "pytest", "output": "passed", "coverage": "95%", "edge_cases": ["empty input", "max size"], "has_verification": True}
            ],
        }
        conflicts = []

        content = generate_review_md(aggregated, conflicts, ".cleave")
        assert "edge" in content.lower()


class TestReunifyWorkspace:
    """Tests for end-to-end reunification workflow."""

    @pytest.fixture
    def workspace(self, tmp_path: Path) -> Path:
        """Create a workspace directory with task files."""
        ws = tmp_path / ".cleave"
        ws.mkdir()
        return ws

    def test_reunifies_successful_tasks(self, workspace: Path) -> None:
        """Should reunify tasks and generate merge/review files."""
        (workspace / "0-task.md").write_text("""
## Result

**Status:** SUCCESS

**Summary:** Task 0 completed.

**Artifacts:**
- src/file0.py
""")
        (workspace / "1-task.md").write_text("""
## Result

**Status:** SUCCESS

**Summary:** Task 1 completed.

**Artifacts:**
- src/file1.py
""")

        result = reunify_workspace(str(workspace))

        assert "error" not in result
        assert result["tasks_found"] == 2
        assert result["rollup_status"] == "SUCCESS"
        assert result["conflicts_found"] == 0
        assert result["ready_to_close"] is True
        assert result["merge_file"] is not None
        assert result["review_file"] is not None
        assert Path(result["merge_file"]).exists()
        assert Path(result["review_file"]).exists()

    def test_detects_conflicts_during_reunify(self, workspace: Path) -> None:
        """Should detect conflicts when reunifying."""
        (workspace / "0-task.md").write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/shared.py
""")
        (workspace / "1-task.md").write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/shared.py
""")

        result = reunify_workspace(str(workspace))

        assert result["conflicts_found"] >= 1
        assert result["ready_to_close"] is False

    def test_not_ready_if_task_failed(self, workspace: Path) -> None:
        """Should not be ready to close if any task failed."""
        (workspace / "0-task.md").write_text("## Result\n\n**Status:** SUCCESS")
        (workspace / "1-task.md").write_text("## Result\n\n**Status:** FAILED")

        result = reunify_workspace(str(workspace))

        assert result["rollup_status"] == "FAILED"
        assert result["ready_to_close"] is False

    def test_skips_review_when_disabled(self, workspace: Path) -> None:
        """Should not generate review.md when write_review=False."""
        (workspace / "0-task.md").write_text("## Result\n\n**Status:** SUCCESS")

        result = reunify_workspace(str(workspace), write_review=False)

        assert result["review_file"] is None
        assert not (workspace / "review.md").exists()

    def test_returns_error_for_missing_workspace(self) -> None:
        """Should return error for non-existent workspace."""
        result = reunify_workspace("/nonexistent/workspace")
        assert "error" in result

    def test_returns_error_for_no_tasks(self, workspace: Path) -> None:
        """Should return error when no task files found."""
        result = reunify_workspace(str(workspace))
        assert "error" in result

    def test_sorts_task_files_numerically(self, workspace: Path) -> None:
        """Should sort task files by numeric ID, not alphabetically."""
        # Create files out of order
        (workspace / "10-task.md").write_text("## Result\n\n**Status:** SUCCESS")
        (workspace / "2-task.md").write_text("## Result\n\n**Status:** SUCCESS")
        (workspace / "1-task.md").write_text("## Result\n\n**Status:** SUCCESS")

        result = reunify_workspace(str(workspace))

        assert result["tasks_found"] == 3
        # Files should be in numeric order: 1, 2, 10
        assert "1-task.md" in result["task_files"][0]
        assert "2-task.md" in result["task_files"][1]
        assert "10-task.md" in result["task_files"][2]

    def test_includes_verification_coverage(self, workspace: Path) -> None:
        """Should include verification coverage in result."""
        (workspace / "0-task.md").write_text("""
## Result

**Status:** SUCCESS

**Verification:**
- Test command: `pytest`
- Test output: passed
""")
        (workspace / "1-task.md").write_text("## Result\n\n**Status:** SUCCESS")

        result = reunify_workspace(str(workspace))

        assert result["verification"]["tasks_with_evidence"] == 1
        assert result["verification"]["tasks_total"] == 2
        assert result["verification"]["coverage"] == "1/2"

    def test_extracts_effort_on_success(self, workspace: Path) -> None:
        """Should auto-extract effort from task results on successful reunify."""
        # Create manifest with directive hash for tracking
        (workspace / "manifest.yaml").write_text("""
version: 1
root_directive: "Test task for effort extraction"
intent:
  goal: "Test"
""")

        # Create task files with effort indicators
        (workspace / "0-task.md").write_text("""
## Result

**Status:** SUCCESS

**Summary:** This was a straightforward implementation with minimal complexity.

**Artifacts:**
- src/simple.py
""")

        result = reunify_workspace(str(workspace))

        assert result["rollup_status"] == "SUCCESS"
        # Auto-extraction should produce effort data
        assert "extracted_effort" in result
        assert result["extracted_effort"][0]["inferred_effort"] == "low"

    def test_accepts_explicit_effort_flag(self, workspace: Path) -> None:
        """Should accept explicit effort override via parameter."""
        (workspace / "0-task.md").write_text("## Result\n\n**Status:** SUCCESS")

        result = reunify_workspace(str(workspace), explicit_effort="high")

        assert result["explicit_effort"] == "high"

    def test_updates_metrics_on_success(self, workspace: Path) -> None:
        """Should update metrics file with effort data when all tasks succeed."""
        from cleave.core.metrics import hash_directive

        directive = "Test task for metrics update"
        directive_hash = hash_directive(directive)

        # Create manifest
        (workspace / "manifest.yaml").write_text(f"""
version: 1
root_directive: "{directive}"
intent:
  goal: "Test"
""")

        # Create metrics with recorded assessment
        (workspace / "metrics.yaml").write_text(f"""
assessments:
  - directive_hash: {directive_hash}
    timestamp: "2024-01-01T00:00:00"
    predicted_complexity: 2.0
    predicted_decision: execute
    pattern_matched: null
    pattern_confidence: 0.0
    systems: 1
    modifiers: []
    actual_effort: null
    actual_success: null
    accuracy_score: null
""")

        # Create successful task
        (workspace / "0-task.md").write_text("""
## Result

**Status:** SUCCESS

**Summary:** Quick fix completed.

**Artifacts:**
- file.py
""")

        result = reunify_workspace(str(workspace))

        assert result["rollup_status"] == "SUCCESS"
        # Metrics should be updated
        from cleave.core.yaml_utils import parse_yaml_simple
        metrics = parse_yaml_simple((workspace / "metrics.yaml").read_text())
        # Check that actual_effort was filled in
        assert metrics["assessments"][0]["actual_effort"] is not None
