"""
Conflict Resolution API Routes for CodeContext server.

Handles conflict detection, resolution, and auto-resolution via LLM.
"""

from fastapi import APIRouter, Request, Query, HTTPException
from fastapi.responses import StreamingResponse

from ..models import ConflictResolveRequest, ApplyDecisionsRequest
from ..services.store_manager import get_store, list_projects, ALL_PROJECTS
from ..utils.formatting import format_memories_datetime, sse_event
from ..services.conflict_resolver import (
    auto_resolve_conflicts,
    separate_by_confidence,
    build_resolution_prompt,
    parse_resolution_response,
    spawn_claude_code as spawn_claude_code_conflict
)


router = APIRouter()


@router.get("/api/conflicts")
async def get_conflicts(
    project: str = Query(...),
    status: str = Query(default="pending")
):
    """Get conflicts for a project (supports ALL_PROJECTS for aggregation)."""
    if project == ALL_PROJECTS:
        # Aggregate conflicts from all projects (same logic as HTML page)
        projects = list_projects()
        all_conflicts = []
        confidence_priority = {"auto": 0, "high": 1, "medium": 2, "low": 3}

        for p in projects:
            store = get_store(p)
            p_conflicts = store.list_conflicts(status=status)
            for c in p_conflicts:
                c["project"] = p
            all_conflicts.extend(p_conflicts)

        # Sort by confidence priority
        all_conflicts.sort(
            key=lambda x: (
                confidence_priority.get(x.get("confidence", "low"), 3),
                -(hash(x.get("created_at", "")) if x.get("created_at") else 0)
            )
        )

        conflicts = format_memories_datetime(all_conflicts)
        # Also format the conflict_with memories
        for c in conflicts:
            if c.get("conflict_with"):
                c["conflict_with"] = format_memories_datetime([c["conflict_with"]])[0]

        return {
            "project": project,
            "status": status,
            "conflicts": conflicts,
            "count": len(conflicts)
        }
    else:
        # Single project
        store = get_store(project)
        conflicts = store.list_conflicts(status=status)
        # Add project field for consistency
        for c in conflicts:
            c["project"] = project
        conflicts = format_memories_datetime(conflicts)
        # Also format the conflict_with memories
        for c in conflicts:
            if c.get("conflict_with"):
                c["conflict_with"] = format_memories_datetime([c["conflict_with"]])[0]

        return {
            "project": project,
            "status": status,
            "conflicts": conflicts,
            "count": len(conflicts)
        }


@router.post("/api/conflicts/{conflict_id}/resolve")
async def resolve_conflict(
    conflict_id: str,
    req: ConflictResolveRequest,
    project: str = Query(...)
):
    """Resolve a conflict by its conflict ID."""
    store = get_store(project)
    result = store.resolve_conflict(
        conflict_id=conflict_id,
        resolution=req.resolution,
        merged_fact=req.merged_fact
    )

    if not result.get("success"):
        raise HTTPException(status_code=400, detail=result.get("error", "Unknown error"))

    return {
        "project": project,
        "conflict_id": conflict_id,
        **result
    }


@router.get("/api/conflicts/auto-resolve/stream")
async def auto_resolve_stream(
    project: str = Query(...)
):
    """
    Stream real-time progress updates during conflict auto-resolution via Server-Sent Events.

    Progress phases:
    1. starting - Initial setup
    2. loading_conflicts - Fetching pending conflicts
    3. loaded_conflicts - Conflicts identified with count
    4. tiering - Separating by confidence level
    5. processing_auto - Auto-resolving simple conflicts
    6. processing_llm - Sending to Claude for analysis
    7. processing_conflict - Per-conflict progress during LLM processing
    8. complete - Finished with results

    Returns: StreamingResponse with SSE events
    """
    async def generate():
        try:
            # Phase 1: Starting
            yield sse_event({
                "phase": "starting",
                "message": "Initializing auto-resolve...",
                "progress": 0
            })

            # Phase 2: Loading conflicts
            yield sse_event({
                "phase": "loading_conflicts",
                "message": "Loading pending conflicts...",
                "progress": 5
            })

            # Get conflicts based on project
            if project == ALL_PROJECTS:
                conflicts = []
                for p in list_projects():
                    store = get_store(p)
                    p_conflicts = store.list_conflicts(status="pending")
                    for c in p_conflicts:
                        c["project"] = p
                    conflicts.extend(p_conflicts)
            else:
                store = get_store(project)
                conflicts = store.list_conflicts(status="pending")
                for c in conflicts:
                    c["project"] = project

            if not conflicts:
                yield sse_event({
                    "phase": "complete",
                    "message": "No pending conflicts to resolve",
                    "progress": 100,
                    "results": {
                        "project": project,
                        "preview": True,
                        "decisions": [],
                        "count": 0,
                        "remaining": 0
                    }
                })
                return

            # Phase 3: Loaded conflicts
            yield sse_event({
                "phase": "loaded_conflicts",
                "message": f"Found {len(conflicts)} pending conflicts",
                "count": len(conflicts),
                "progress": 15
            })

            # Phase 4: Tiering
            yield sse_event({
                "phase": "tiering",
                "message": "Analyzing conflict complexity...",
                "progress": 20
            })

            auto_resolvable, llm_batch, low_confidence = separate_by_confidence(conflicts)

            yield sse_event({
                "phase": "tiered",
                "message": f"Sending {len(llm_batch)} conflicts to LLM for analysis",
                "tiers": {
                    "auto": len(auto_resolvable),  # Always 0 - kept for API compatibility
                    "llm_batch": len(llm_batch),
                    "low_confidence": len(low_confidence)
                },
                "progress": 25
            })

            # All conflicts go to LLM - no auto-processing
            all_decisions = []
            max_batch = 10

            # Phase 6: Process LLM conflicts
            remaining_budget = max_batch - len(all_decisions)

            if remaining_budget > 0:
                llm_conflicts = (llm_batch + low_confidence)[:remaining_budget]

                if llm_conflicts:
                    yield sse_event({
                        "phase": "processing_llm",
                        "message": f"Sending {len(llm_conflicts)} conflicts to Claude for analysis...",
                        "count": len(llm_conflicts),
                        "progress": 45
                    })

                    # Build prompt and call LLM
                    prompt = build_resolution_prompt(llm_conflicts)

                    # Extract project for session logs
                    llm_project = llm_conflicts[0].get("project", "_global")

                    try:
                        response = await spawn_claude_code_conflict(prompt, project=llm_project)

                        yield sse_event({
                            "phase": "llm_complete",
                            "message": "Claude analysis complete",
                            "progress": 80
                        })

                        llm_resolutions = parse_resolution_response(response)

                        # Process each resolution
                        for i, resolution in enumerate(llm_resolutions):
                            conflict_id = resolution.get("conflict_id")
                            conflict = next((c for c in llm_conflicts if c.get("id") == conflict_id), None)

                            if conflict:
                                all_decisions.append({
                                    "conflict_id": conflict_id,
                                    "decision": resolution.get("decision"),
                                    "reasoning": resolution.get("reasoning", ""),
                                    "merged_fact": resolution.get("merged_fact"),
                                    "auto_resolved": False,
                                    "conflict": conflict
                                })

                                # Progress update per conflict (80-95%)
                                conflict_progress = 80 + (i + 1) / len(llm_resolutions) * 15
                                yield sse_event({
                                    "phase": "processing_conflict",
                                    "message": f"Processed conflict {i+1}/{len(llm_resolutions)}",
                                    "current": i + 1,
                                    "total": len(llm_resolutions),
                                    "conflict_id": conflict_id,
                                    "decision": resolution.get("decision"),
                                    "progress": int(conflict_progress)
                                })

                    except Exception as e:
                        # On LLM failure, default to keep_both
                        for c in llm_conflicts:
                            all_decisions.append({
                                "conflict_id": c.get("id"),
                                "decision": "keep_both",
                                "reasoning": f"LLM processing failed: {str(e)[:50]}",
                                "auto_resolved": True,
                                "error": True,
                                "conflict": c
                            })

            # Calculate remaining unprocessed conflicts
            total_conflicts = len(conflicts)
            processed = len(all_decisions)
            remaining = max(0, total_conflicts - processed)

            # Phase 7: Complete
            yield sse_event({
                "phase": "complete",
                "message": f"Processed {processed} conflicts",
                "progress": 100,
                "results": {
                    "project": project,
                    "preview": True,
                    "decisions": all_decisions,
                    "count": processed,
                    "remaining": remaining
                }
            })

        except Exception as e:
            import traceback
            print(f"[AutoResolve Stream] Error: {e}")
            print(f"[AutoResolve Stream] Traceback: {traceback.format_exc()}")
            yield sse_event({
                "phase": "error",
                "message": f"Auto-resolve failed: {str(e)}",
                "progress": 0
            })

    return StreamingResponse(
        generate(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no"
        }
    )


@router.post("/api/conflicts/auto-resolve")
async def auto_resolve_all_conflicts(
    project: str = Query(...),
    preview: bool = Query(default=False),
    request: Request = None
):
    """
    Auto-resolve all pending conflicts using Claude Code CLI.

    Spawns Claude Code CLI as subprocess to intelligently decide
    the best resolution for each conflict. Processes up to 10
    conflicts per batch.

    Args:
        project: The project name (or __all__ for all projects)
        preview: If True, return proposed decisions without applying them
        request body: Optional JSON with exclude_ids to skip already-processed conflicts
    """
    if project == ALL_PROJECTS:
        # Aggregate conflicts from all projects
        conflicts = []
        for p in list_projects():
            store = get_store(p)
            p_conflicts = store.list_conflicts(status="pending")
            for c in p_conflicts:
                c["project"] = p
            conflicts.extend(p_conflicts)
    else:
        store = get_store(project)
        conflicts = store.list_conflicts(status="pending")
        # Add project field for consistency
        for c in conflicts:
            c["project"] = project

    # Filter out already-processed conflicts (for multi-batch preview)
    exclude_ids = set()
    if request:
        try:
            body = await request.json()
            exclude_ids = set(body.get("exclude_ids", []))
        except:
            pass  # No body or invalid JSON is fine

    if exclude_ids:
        conflicts = [c for c in conflicts if c.get("id") not in exclude_ids]

    if not conflicts:
        return {
            "project": project,
            "resolved": 0,
            "count": 0,
            "message": "No pending conflicts to resolve"
        }

    try:
        # Call auto_resolve module (spawns Claude CLI)
        result = await auto_resolve_conflicts(conflicts)

        # Preview mode: return decisions WITHOUT applying them
        if preview:
            preview_data = []
            for resolution in result.get("resolutions", []):
                conflict_id = resolution.get("conflict_id")
                # Find the full conflict details for display
                conflict = next(
                    (c for c in conflicts if c["id"] == conflict_id),
                    None
                )
                # Skip if conflict not found (LLM hallucinated an ID)
                if not conflict:
                    print(f"[AutoResolve] Warning: conflict {conflict_id} not found, skipping")
                    continue
                preview_data.append({
                    "conflict_id": conflict_id,
                    "decision": resolution.get("decision"),
                    "reasoning": resolution.get("reasoning", ""),
                    "merged_fact": resolution.get("merged_fact"),
                    "conflict": conflict  # Full conflict for modal display
                })
            return {
                "project": project,
                "preview": True,
                "decisions": preview_data,
                "count": len(preview_data),
                "remaining": result.get("remaining", 0)
            }

        # Apply mode: execute each resolution decision
        applied = []
        errors = []

        for resolution in result.get("resolutions", []):
            conflict_id = resolution.get("conflict_id")
            decision = resolution.get("decision")
            merged_fact = resolution.get("merged_fact")

            # Find the conflict to get its project
            conflict = next((c for c in conflicts if c["id"] == conflict_id), None)
            conflict_project = conflict.get("project", project) if conflict else project

            # Get the correct store for this conflict
            conflict_store = get_store(conflict_project)

            try:
                resolve_result = conflict_store.resolve_conflict(
                    memory_id=conflict_id,
                    resolution=decision,
                    merged_fact=merged_fact
                )

                if resolve_result.get("success"):
                    applied.append({
                        "conflict_id": conflict_id,
                        "decision": decision,
                        "reasoning": resolution.get("reasoning", "")
                    })
                else:
                    errors.append({
                        "conflict_id": conflict_id,
                        "error": resolve_result.get("error", "Unknown error")
                    })
            except Exception as e:
                errors.append({
                    "conflict_id": conflict_id,
                    "error": str(e)
                })

        return {
            "project": project,
            "resolved": len(applied),
            "processed": result.get("processed", 0),
            "total": result.get("total", 0),
            "remaining": result.get("remaining", 0),
            "applied": applied,
            "errors": errors if errors else None
        }

    except RuntimeError as e:
        raise HTTPException(status_code=500, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Auto-resolve failed: {str(e)}")


@router.post("/api/conflicts/apply-decisions")
async def apply_decisions(
    req: ApplyDecisionsRequest,
    project: str = Query(...)
):
    """
    Apply user-approved conflict resolution decisions.

    This endpoint applies decisions that were previewed and possibly
    modified by the user in the preview modal.
    """
    applied = []
    errors = []

    for d in req.decisions:
        conflict_id = d.get("conflict_id")
        decision = d.get("decision")
        merged_fact = d.get("merged_fact")

        # Get the project for this conflict (from the conflict object or from parameter)
        # Use `or {}` to handle explicit None values, not just missing keys
        conflict = d.get("conflict") or {}
        conflict_project = conflict.get("project")

        # When ALL_PROJECTS, each decision must have its own project embedded
        if project == ALL_PROJECTS:
            if not conflict_project or conflict_project == ALL_PROJECTS:
                errors.append({
                    "conflict_id": conflict_id,
                    "error": "Missing project for conflict in All Projects mode"
                })
                continue
        else:
            # Single project mode - use the URL parameter if not embedded
            conflict_project = conflict_project or project

        decision_store = get_store(conflict_project)

        try:
            result = decision_store.resolve_conflict(
                conflict_id=conflict_id,
                resolution=decision,
                merged_fact=merged_fact
            )

            if result.get("success"):
                applied.append(conflict_id)
            else:
                errors.append({
                    "conflict_id": conflict_id,
                    "error": result.get("error", "Unknown error")
                })
        except Exception as e:
            errors.append({
                "conflict_id": conflict_id,
                "error": str(e)
            })

    return {
        "project": project,
        "applied": len(applied),
        "errors": errors if errors else None
    }
