"""Tests for cleave conflicts module - conflict detection during reunification."""

from __future__ import annotations

from pathlib import Path

import pytest

from cleave.core.conflicts import (
    FILE_CLAIM_PATTERNS,
    Conflict,
    detect_conflicts,
    parse_task_result,
)


class TestFileClamPatterns:
    """Tests for improved file claim regex patterns."""

    def test_pattern_matches_quoted_paths(self) -> None:
        """Should match quoted paths (handles spaces)."""
        import re

        section = '- `src/my file.py`\n- "path/with spaces/file.ts"'
        matches = []
        for pattern in FILE_CLAIM_PATTERNS:
            matches.extend(re.findall(pattern, section))
        assert "src/my file.py" in matches
        assert "path/with spaces/file.ts" in matches

    def test_pattern_matches_unquoted_paths(self) -> None:
        """Should match standard unquoted paths with extensions."""
        import re

        section = "- src/utils/helper.py\n- tests/test_main.ts"
        matches = []
        for pattern in FILE_CLAIM_PATTERNS:
            matches.extend(re.findall(pattern, section))
        assert "src/utils/helper.py" in matches
        assert "tests/test_main.ts" in matches

    def test_pattern_matches_directory_paths(self) -> None:
        """Should match directory paths ending in /."""
        import re

        section = "- src/build/\n- dist/output/"
        matches = []
        for pattern in FILE_CLAIM_PATTERNS:
            matches.extend(re.findall(pattern, section))
        assert "src/build/" in matches
        assert "dist/output/" in matches

    def test_pattern_matches_absolute_paths(self) -> None:
        """Should match absolute paths."""
        import re

        section = "- /home/user/project/file.py\n- /var/log/app.log"
        matches = []
        for pattern in FILE_CLAIM_PATTERNS:
            matches.extend(re.findall(pattern, section))
        # Should find these in absolute path pattern
        assert any("/home/user/project/file.py" in m for m in matches)


class TestParseTaskResult:
    """Tests for parsing task result markdown files."""

    @pytest.fixture
    def temp_task_file(self, tmp_path: Path) -> Path:
        """Create a temporary task file for testing."""
        return tmp_path / "0-task.md"

    def test_parses_success_status(self, temp_task_file: Path) -> None:
        """Should parse SUCCESS status."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Summary:** Task completed successfully.
""")
        result = parse_task_result(str(temp_task_file))
        assert result["status"] == "SUCCESS"

    def test_parses_partial_status(self, temp_task_file: Path) -> None:
        """Should parse PARTIAL status."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** PARTIAL

**Summary:** Partially completed.
""")
        result = parse_task_result(str(temp_task_file))
        assert result["status"] == "PARTIAL"

    def test_parses_failed_status(self, temp_task_file: Path) -> None:
        """Should parse FAILED status."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** FAILED

**Summary:** Task failed.
""")
        result = parse_task_result(str(temp_task_file))
        assert result["status"] == "FAILED"

    def test_parses_pending_by_default(self, temp_task_file: Path) -> None:
        """Should default to PENDING if no status found."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Summary:** No status specified.
""")
        result = parse_task_result(str(temp_task_file))
        assert result["status"] == "PENDING"

    def test_parses_summary(self, temp_task_file: Path) -> None:
        """Should extract summary text."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Summary:** Implemented the authentication module with JWT tokens.

**Artifacts:**
- file.py
""")
        result = parse_task_result(str(temp_task_file))
        assert result["summary"] is not None
        assert "authentication module" in result["summary"]

    def test_parses_file_claims(self, temp_task_file: Path) -> None:
        """Should extract file claims from Artifacts section."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Artifacts:**
- src/auth/login.py
- src/auth/logout.py
- `tests/test_auth.py`
""")
        result = parse_task_result(str(temp_task_file))
        assert len(result["file_claims"]) >= 2
        assert "src/auth/login.py" in result["file_claims"]

    def test_parses_interfaces_published(self, temp_task_file: Path) -> None:
        """Should extract interface signatures."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Interfaces Published:**
- `login(username: str, password: str) -> Token`
- `logout(token: str) -> bool`
""")
        result = parse_task_result(str(temp_task_file))
        assert len(result["interfaces_published"]) == 2
        assert any("login" in i for i in result["interfaces_published"])

    def test_parses_decisions(self, temp_task_file: Path) -> None:
        """Should extract decisions made."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Decisions Made:**
- Used JWT for token format
- Chose bcrypt for password hashing
""")
        result = parse_task_result(str(temp_task_file))
        assert len(result["decisions"]) >= 2
        assert any("JWT" in d for d in result["decisions"])

    def test_parses_assumptions(self, temp_task_file: Path) -> None:
        """Should extract assumptions."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Assumptions:**
- Database connection is available
- Redis is running on localhost
""")
        result = parse_task_result(str(temp_task_file))
        assert len(result["assumptions"]) >= 2

    def test_parses_verification_command(self, temp_task_file: Path) -> None:
        """Should extract test command."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Verification:**
- Test command: `pytest tests/test_auth.py -v`
- Test output: All 5 tests passed
- Coverage: 87%
""")
        result = parse_task_result(str(temp_task_file))
        assert result["verification"]["command"] == "pytest tests/test_auth.py -v"
        assert "passed" in result["verification"]["output"]
        assert "87%" in result["verification"]["coverage"]

    def test_parses_verification_template_format(self, temp_task_file: Path) -> None:
        """Should parse the task template format (- Command: / - Output: / - Edge cases:)."""
        temp_task_file.write_text("""
# Task 0: Plugin Discovery

## Result

**Status:** SUCCESS

**Verification:**
- Command: `pytest tests/test_plugin_discovery.py -v`
- Output: 21 tests passed covering plugin discovery and validation
- Edge cases:
  - Plugin discovery from filesystem
  - Registry management
  - Error handling for invalid plugins
""")
        result = parse_task_result(str(temp_task_file))
        assert result["verification"]["command"] == "pytest tests/test_plugin_discovery.py -v"
        assert "21 tests passed" in result["verification"]["output"]
        assert len(result["verification"]["edge_cases"]) == 3
        assert "Plugin discovery from filesystem" in result["verification"]["edge_cases"]

    def test_verification_anchored_to_section(self, temp_task_file: Path) -> None:
        """Stray 'Output:' text outside Verification block should not be captured."""
        temp_task_file.write_text("""
# Task 0: Output Formatting

## Result

**Status:** SUCCESS

**Summary:** Implemented output formatting. Output: should be empty when no input is provided.

**Verification:**
- Command: `pytest tests/test_output.py -v`
- Output: 12 tests passed
- Edge cases:
  - Empty input produces no output
  - Large input handled correctly

**Alignment:** Output: format matches spec
""")
        result = parse_task_result(str(temp_task_file))
        # Should capture the Verification section's Output, not the Summary or Alignment one
        assert result["verification"]["output"] == "12 tests passed"
        assert result["verification"]["command"] == "pytest tests/test_output.py -v"
        assert len(result["verification"]["edge_cases"]) == 2

    def test_parses_alignment_check(self, temp_task_file: Path) -> None:
        """Should extract alignment check results."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** SUCCESS

**Alignment Check:**
- Aligns with root goal? YES - Implements user authentication
- Violates constraints? NO
- Success criteria achievable? YES - All criteria met
""")
        result = parse_task_result(str(temp_task_file))
        assert result["alignment"]["aligns_with_goal"] == "YES"
        assert result["alignment"]["violates_constraints"] == "NO"
        assert result["alignment"]["success_achievable"] == "YES"

    def test_handles_missing_file(self) -> None:
        """Should return error dict for missing file."""
        result = parse_task_result("/nonexistent/path/task.md")
        assert result["status"] == "NOT_FOUND"
        assert "error" in result

    def test_skips_placeholder_values(self, temp_task_file: Path) -> None:
        """Should skip [placeholder] values."""
        temp_task_file.write_text("""
# Task 0: Test Task

## Result

**Status:** PENDING

**Summary:** [What was accomplished]

**Decisions Made:**
- [Key choices with rationale]
""")
        result = parse_task_result(str(temp_task_file))
        assert result["summary"] is None
        assert len(result["decisions"]) == 0


class TestDetectConflicts:
    """Tests for 4-step conflict detection algorithm."""

    @pytest.fixture
    def task_files(self, tmp_path: Path) -> tuple[Path, Path]:
        """Create two task files for conflict testing."""
        task0 = tmp_path / "0-task.md"
        task1 = tmp_path / "1-task.md"
        return task0, task1

    def test_detects_file_overlap(self, task_files: tuple[Path, Path]) -> None:
        """Should detect when multiple tasks modify same file."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/shared/utils.py
- src/auth/login.py
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/shared/utils.py
- src/api/routes.py
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        file_overlaps = [c for c in conflicts if c.type == "file_overlap"]
        assert len(file_overlaps) >= 1
        assert any("utils.py" in c.description for c in file_overlaps)

    def test_detects_decision_contradiction_redis_memcached(
        self, task_files: tuple[Path, Path]
    ) -> None:
        """Should detect Redis vs Memcached contradiction."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Used Redis for caching layer
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Chose Memcached for cache implementation
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        contradictions = [c for c in conflicts if c.type == "decision_contradiction"]
        assert len(contradictions) >= 1

    def test_detects_decision_contradiction_rest_graphql(
        self, task_files: tuple[Path, Path]
    ) -> None:
        """Should detect REST vs GraphQL contradiction."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Chose REST API for simplicity
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Implemented GraphQL endpoints for flexibility
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        contradictions = [c for c in conflicts if c.type == "decision_contradiction"]
        assert len(contradictions) >= 1

    def test_no_contradiction_when_same_sibling(
        self, task_files: tuple[Path, Path]
    ) -> None:
        """Should not flag contradiction if same sibling mentions both (choosing one over other)."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Chose Redis instead of Memcached for better data structures
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Used default logging configuration
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        # Task 0 mentions both but is choosing Redis over Memcached.
        # Task 1 is unrelated — no cross-sibling contradiction should fire.
        redis_memcached = [
            c
            for c in conflicts
            if c.type == "decision_contradiction"
            and ("redis" in c.description.lower() or "memcached" in c.description.lower())
        ]
        assert len(redis_memcached) == 0, (
            "Same-sibling choice (Redis over Memcached) should not be flagged as cross-sibling contradiction"
        )

    def test_detects_interface_mismatch(self, task_files: tuple[Path, Path]) -> None:
        """Should detect conflicting interface signatures."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Interfaces Published:**
- `getUser(id: int) -> User`
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Interfaces Published:**
- `getUser(id: str, include_deleted: bool) -> User | None`
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        mismatches = [c for c in conflicts if c.type == "interface_mismatch"]
        assert len(mismatches) >= 1
        assert any("getUser" in c.description for c in mismatches)

    def test_no_interface_mismatch_for_same_signature(
        self, task_files: tuple[Path, Path]
    ) -> None:
        """Should not flag matching interface signatures."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Interfaces Published:**
- `getUser(id: int) -> User`
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Interfaces Published:**
- `getUser(id: int) -> User`
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        mismatches = [c for c in conflicts if c.type == "interface_mismatch"]
        assert len(mismatches) == 0

    def test_detects_assumption_violation(self, task_files: tuple[Path, Path]) -> None:
        """Should detect when assumption contradicts sibling's decision."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Assumptions:**
- Database does not require authentication
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Decisions Made:**
- Added authentication requirement via not-null constraint on auth_token column
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        # Task 0 assumes no auth; task 1 decided to require it.
        # The detector should surface this as an assumption_violation.
        assumption_violations = [c for c in conflicts if c.type == "assumption_violation"]
        assert len(assumption_violations) >= 1, (
            "Assumption 'does not require authentication' should conflict with "
            "decision 'Added authentication requirement'"
        )

    def test_returns_empty_list_for_no_conflicts(
        self, task_files: tuple[Path, Path]
    ) -> None:
        """Should return empty list when no conflicts exist."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/auth/login.py

**Decisions Made:**
- Used JWT tokens
""")
        task1.write_text("""
## Result

**Status:** SUCCESS

**Artifacts:**
- src/api/routes.py

**Decisions Made:**
- Added rate limiting
""")
        conflicts = detect_conflicts([str(task0), str(task1)])
        # Should have no file overlap, no decision contradiction
        file_overlaps = [c for c in conflicts if c.type == "file_overlap"]
        assert len(file_overlaps) == 0

    def test_handles_missing_task_file(self, task_files: tuple[Path, Path]) -> None:
        """Should handle missing task files gracefully without raising."""
        task0, task1 = task_files
        task0.write_text("""
## Result

**Status:** SUCCESS
""")
        # task1 doesn't exist — detect_conflicts must not raise
        conflicts = detect_conflicts([str(task0), str(task1)])
        # Return type is always a list (possibly empty)
        assert isinstance(conflicts, list)


class TestConflictDataclass:
    """Tests for Conflict dataclass."""

    def test_conflict_creation(self) -> None:
        """Should create Conflict with all fields."""
        conflict = Conflict(
            type="file_overlap",
            description="Multiple children modified src/utils.py",
            involved=[0, 1],
            resolution="3way_merge",
        )
        assert conflict.type == "file_overlap"
        assert len(conflict.involved) == 2
        assert conflict.resolution == "3way_merge"

    def test_conflict_types(self) -> None:
        """Should support all four conflict types."""
        types = ["file_overlap", "decision_contradiction", "interface_mismatch", "assumption_violation"]
        for t in types:
            conflict = Conflict(type=t, description="test", involved=[0, 1], resolution="test")
            assert conflict.type == t
