"""Conflicts module - conflict detection during reunification."""

from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path


@dataclass
class Conflict:
    """Detected conflict between child tasks."""

    type: str  # file_overlap, decision_contradiction, interface_mismatch, assumption_violation
    description: str
    involved: list[int]  # Task IDs involved
    resolution: str  # Suggested resolution strategy


def parse_task_result(task_path: str) -> dict:
    """Parse task.md to extract result info for conflict detection.

    Extracts:
    - Status (SUCCESS/PARTIAL/FAILED/PENDING)
    - Interfaces published (function signatures)
    - File claims (files modified/created)
    - Decisions made
    - Assumptions
    - Verification info
    - Alignment check
    """
    path = Path(task_path)
    if not path.exists():
        return {"error": f"Task file not found: {task_path}", "status": "NOT_FOUND"}

    content = path.read_text()

    result: dict = {
        "path": str(path),
        "status": "PENDING",
        "summary": None,
        "interfaces_published": [],
        "decisions": [],
        "assumptions": [],
        "file_claims": [],
        "verification": {
            "command": None,
            "output": None,
            "coverage": None,
            "edge_cases": [],
        },
        "alignment": {
            "aligns_with_goal": None,
            "violates_constraints": None,
            "success_achievable": None,
        },
    }

    # Extract status
    if "**Status:** SUCCESS" in content:
        result["status"] = "SUCCESS"
    elif "**Status:** PARTIAL" in content:
        result["status"] = "PARTIAL"
    elif "**Status:** FAILED" in content:
        result["status"] = "FAILED"

    # Extract summary
    summary_match = re.search(r"\*\*Summary:\*\*\s*(.+?)(?:\n\*\*|\n##|$)", content, re.DOTALL)
    if summary_match:
        summary = summary_match.group(1).strip()
        # Skip placeholder
        if not summary.startswith("["):
            result["summary"] = summary

    # Extract interfaces: `function_name(params) -> return_type`
    interface_pattern = r"`([a-zA-Z_][a-zA-Z0-9_]*\([^)]*\)\s*->\s*[^`]+)`"
    result["interfaces_published"] = re.findall(interface_pattern, content)

    # Extract file claims from "Artifacts" section
    artifacts_match = re.search(r"\*\*Artifacts:\*\*\s*\n((?:\s*-\s*.+\n?)+)", content)
    if artifacts_match:
        section = artifacts_match.group(1)
        # Extract paths from list items (look for path-like strings)
        paths = re.findall(r'-\s*[`"]?([a-zA-Z0-9_./-]+\.[a-zA-Z0-9]+)[`"]?', section)
        result["file_claims"].extend(paths)

    # Deduplicate file claims
    result["file_claims"] = list(set(result["file_claims"]))

    # Extract decisions from "Decisions Made" section
    decisions_match = re.search(r"\*\*Decisions Made:\*\*\s*\n((?:\s*-\s*.+\n?)+)", content)
    if decisions_match:
        decisions = re.findall(r"-\s*(.+)", decisions_match.group(1))
        result["decisions"] = [d.strip() for d in decisions if d.strip() and not d.startswith("[")]

    # Extract assumptions from "Assumptions" section
    assumptions_match = re.search(r"\*\*Assumptions:\*\*\s*\n((?:\s*-\s*.+\n?)+)", content)
    if assumptions_match:
        assumptions = re.findall(r"-\s*(.+)", assumptions_match.group(1))
        result["assumptions"] = [
            a.strip() for a in assumptions if a.strip() and not a.startswith("[")
        ]

    # Extract verification info
    cmd_match = re.search(r"(?:Test|Verification) command:\s*`([^`]+)`", content)
    if cmd_match:
        cmd = cmd_match.group(1)
        # Skip placeholder values
        if not cmd.startswith("[") and "command to" not in cmd.lower():
            result["verification"]["command"] = cmd

    # Test/Verification output
    output_match = re.search(r"(?:Test|Verification) output:\s*(.+?)(?:\n|$)", content)
    if output_match:
        result["verification"]["output"] = output_match.group(1).strip()

    # Coverage
    coverage_match = re.search(r"Coverage:\s*(\d+%|N/A|.+?)(?:\n|$)", content)
    if coverage_match:
        result["verification"]["coverage"] = coverage_match.group(1).strip()

    # Edge cases tested - supports both list format and comma-separated
    edge_match = re.search(r"Edge cases tested:\s*(.+?)(?:\n\*\*|\n##|$)", content, re.DOTALL)
    if edge_match:
        edge_text = edge_match.group(1).strip()
        # Try list format first (- item)
        edges = re.findall(r"-\s*(.+)", edge_text)
        if not edges and edge_text and not edge_text.startswith("["):
            # Fall back to comma-separated on single line
            edges = [e.strip() for e in edge_text.split(",") if e.strip()]
        result["verification"]["edge_cases"] = [
            e.strip() for e in edges if e.strip() and not e.startswith("[")
        ]

    # Extract alignment check
    align_goal = re.search(
        r"Aligns with root goal\?\s*(YES|NO)(?:\s*-\s*(.+?))?(?:\n|$)", content, re.IGNORECASE
    )
    if align_goal:
        result["alignment"]["aligns_with_goal"] = align_goal.group(1).upper()

    violates = re.search(
        r"Violates constraints\?\s*(YES|NO)(?:\s*-\s*(.+?))?(?:\n|$)", content, re.IGNORECASE
    )
    if violates:
        result["alignment"]["violates_constraints"] = violates.group(1).upper()

    success = re.search(
        r"Success criteria achievable\?\s*(YES|NO)(?:\s*-\s*(.+?))?(?:\n|$)", content, re.IGNORECASE
    )
    if success:
        result["alignment"]["success_achievable"] = success.group(1).upper()

    return result


def detect_conflicts(result_paths: list[str]) -> list[Conflict]:
    """Run 4-step conflict detection algorithm.

    Steps per SKILL.md:
    1. File Overlap - Multiple children modified same file
    2. Decision Contradiction - Incompatible choices (e.g., "use Redis" vs "use Memcached")
    3. Interface Mismatch - Published signature differs from consumed expectation
    4. Assumption Violation - Contradicts parent directive or sibling work
    """
    conflicts: list[Conflict] = []
    results = [parse_task_result(path) for path in result_paths]

    # Step 1: File Overlap Detection
    file_claims: dict[str, int] = {}
    for i, result in enumerate(results):
        for file_path in result.get("file_claims", []):
            if file_path in file_claims:
                conflicts.append(
                    Conflict(
                        type="file_overlap",
                        description=f"Multiple children modified {file_path}",
                        involved=[file_claims[file_path], i],
                        resolution="3way_merge",
                    )
                )
            else:
                file_claims[file_path] = i

    # Step 2: Decision Contradiction Detection
    contradiction_pairs = [
        (["redis", "use redis"], ["memcached", "use memcached"]),
        (["sql", "postgresql", "mysql"], ["nosql", "mongodb", "dynamodb"]),
        (["sync", "synchronous"], ["async", "asynchronous"]),
        (["rest", "restful"], ["graphql", "grpc"]),
        (["jwt"], ["session", "cookie-based"]),
    ]
    rejection_phrases = ["instead of", "not ", "rather than", "over ", "rejected"]

    all_decisions: list[tuple[int, str]] = []
    for i, result in enumerate(results):
        for decision in result.get("decisions", []):
            all_decisions.append((i, decision.lower()))

    for terms_a, terms_b in contradiction_pairs:
        # Find siblings that chose option A (not just mentioned it in rejection)
        siblings_a = []
        for i, d in all_decisions:
            has_term_a = any(t in d for t in terms_a)
            rejected_a = any(t in d for t in terms_b) and any(rej in d for rej in rejection_phrases)
            if has_term_a and not rejected_a:
                siblings_a.append(i)

        # Find siblings that chose option B
        siblings_b = []
        for i, d in all_decisions:
            has_term_b = any(t in d for t in terms_b)
            rejected_b = any(t in d for t in terms_a) and any(rej in d for rej in rejection_phrases)
            if has_term_b and not rejected_b:
                siblings_b.append(i)

        # Only flag as conflict if DIFFERENT siblings made opposing choices
        different_siblings_a = set(siblings_a) - set(siblings_b)
        different_siblings_b = set(siblings_b) - set(siblings_a)

        if different_siblings_a and different_siblings_b:
            conflicts.append(
                Conflict(
                    type="decision_contradiction",
                    description=f"Contradictory decisions: {terms_a[0]} vs {terms_b[0]}",
                    involved=list(different_siblings_a | different_siblings_b),
                    resolution="escalate_to_parent",
                )
            )

    # Step 3: Interface Mismatch Detection
    published: dict[str, dict] = {}
    for i, result in enumerate(results):
        for interface in result.get("interfaces_published", []):
            func_name = interface.split("(")[0] if "(" in interface else interface
            if func_name in published and published[func_name]["signature"] != interface:
                conflicts.append(
                    Conflict(
                        type="interface_mismatch",
                        description=f"Interface '{func_name}' has conflicting signatures",
                        involved=[published[func_name]["source"], i],
                        resolution="adapter_required",
                    )
                )
            else:
                published[func_name] = {"signature": interface, "source": i}

    # Step 4: Assumption Violation Detection
    all_assumptions: list[tuple[int, str]] = []
    for i, result in enumerate(results):
        for assumption in result.get("assumptions", []):
            all_assumptions.append((i, assumption.lower()))

    for ass_idx, assumption in all_assumptions:
        for dec_idx, decision in all_decisions:
            if ass_idx != dec_idx:  # Different siblings
                # Check for negation patterns
                if ("not " in assumption and any(w in decision for w in assumption.replace("not ", "").split())) or (
                    "not " in decision and any(w in assumption for w in decision.replace("not ", "").split())
                ):
                    conflicts.append(
                        Conflict(
                            type="assumption_violation",
                            description=f"Sibling {ass_idx}'s assumption may conflict with sibling {dec_idx}'s decision",
                            involved=[ass_idx, dec_idx],
                            resolution="verify_with_parent",
                        )
                    )

    return conflicts
