"""Dispatch and prompt-preview endpoints."""

from typing import Any

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field

from proxym.dashboard._environments_api import get_tool_environment_snapshot
from proxym.tools import ToolRegistry
from proxym.dashboard.tools._helpers import _get_ticket, _default_project_dir
from proxym.domain.events import JobDispatched, TaskAssigned

router = APIRouter(tags=["tools"])


class DispatchRequest(BaseModel):
    ticket_id: str = Field(..., description="Ticket ID to dispatch")
    tool: str = Field(..., description="Tool name (aider, claude-code, ...)")
    mode: str = Field("", description="Agent mode (code, architect, debug, ask)")
    model: str = Field("", description="Model alias or full name")
    project_dir: str = Field("", description="Project directory")
    instance: str = Field("", description="Tool instance name (host, docker, etc.)")


class DryRunRequest(BaseModel):
    ticket_id: str = Field(..., description="Ticket ID to validate")
    tool: str = Field(..., description="Tool name (aider, claude-code, ...)")
    mode: str = Field("", description="Agent mode (code, architect, debug, ask)")
    model: str = Field("", description="Model alias or full name")
    project_dir: str = Field("", description="Project directory (optional)")


class PreviewPromptRequest(BaseModel):
    ticket_id: str = Field(..., description="Ticket ID")
    tool: str = Field(..., description="Tool name")
    mode: str = Field("", description="Agent mode")


@router.post("/prompt/preview")
async def preview_prompt(req: PreviewPromptRequest) -> dict[str, Any]:
    """Preview the prompt that would be sent to a tool for a ticket."""
    tool = ToolRegistry.get().get_tool(req.tool)
    if not tool:
        raise HTTPException(404, f"Tool '{req.tool}' not found")

    from proxym.tools.prompt_builder import build_prompt_dict
    ticket = _get_ticket(req.ticket_id)
    project_dir = _default_project_dir(ticket.project_id)
    return build_prompt_dict(ticket, tool, req.mode, project_dir=project_dir)


@router.post("/dispatch/dry-run")
async def dispatch_dry_run(req: DryRunRequest) -> dict[str, Any]:
    """Pre-flight validation: check project, tool health, and dependencies without dispatching.

    Returns a detailed plan including:
    - Ticket summary
    - Tool status and health
    - Project existence and path resolution
    - Any blocking dependencies (e.g., unmet depends_on)
    - Preview of the prompt that would be sent
    """
    errors = []
    warnings = []

    # 1. Validate ticket exists
    ticket = _get_ticket(req.ticket_id)

    # 2. Validate tool exists and is healthy
    tool = ToolRegistry.get().get_tool(req.tool)
    if not tool:
        errors.append(f"Tool '{req.tool}' not found")
    else:
        # Check tool health
        if not tool.is_available:
            errors.append(f"Tool '{req.tool}' is not available on this host")

    # 3. Validate project
    project_dir = req.project_dir
    if not project_dir and ticket.project_id:
        project_dir = _default_project_dir(ticket.project_id)

    if project_dir and not __import__("os").path.exists(project_dir):
        errors.append(f"Project directory does not exist: {project_dir}")

    # 4. Check dependency chain (depends_on tickets)
    blocking_deps = []
    if ticket.depends_on:
        from proxym.dashboard._tickets_api import _tickets
        mgr = _tickets()
        for dep_id in ticket.depends_on:
            dep = mgr.get_ticket(dep_id)
            if dep and dep.status.value != "done":
                blocking_deps.append(f"{dep_id} ({dep.status.value})")

    if blocking_deps:
        errors.append(f"Ticket has unmet dependencies: {', '.join(blocking_deps)}")

    # 5. Build prompt preview
    prompt_preview = {}
    if tool and ticket:
        try:
            from proxym.tools.prompt_builder import build_prompt_dict
            prompt_preview = build_prompt_dict(ticket, tool, req.mode, project_dir=project_dir)
            # Auto-read context files and attach content
            if ticket.context_files:
                prompt_preview["context_files_content"] = _read_context_files(
                    ticket.context_files, project_dir or "."
                )
        except Exception as e:
            warnings.append(f"Could not build prompt preview: {e}")

    # Determine if dispatch would succeed
    can_dispatch = len(errors) == 0

    return {
        "dry_run": True,
        "can_dispatch": can_dispatch,
        "ticket": ticket.summary() if ticket else None,
        "tool": tool.summary() if tool else None,
        "project_dir": project_dir,
        "errors": errors,
        "warnings": warnings,
        "blocking_dependencies": blocking_deps,
        "prompt_preview": prompt_preview,
        "next_steps": (
            ["Fix errors above, then dispatch"] if errors else
            (["Review warnings, then dispatch"] if warnings else
             ["Ready to dispatch: POST /dashboard/tools/dispatch"])
        ),
    }


def _read_context_files(context_files: list[str], project_dir: str) -> dict[str, str]:
    """Read context files and return their contents.

    Args:
        context_files: List of file paths relative to project_dir
        project_dir: Base directory for resolving paths

    Returns:
        Dict mapping file path to file content (or error message)
    """
    import os

    contents = {}
    for filepath in context_files:
        full_path = os.path.join(project_dir, filepath)
        try:
            if os.path.exists(full_path) and os.path.isfile(full_path):
                with open(full_path, 'r', encoding='utf-8', errors='ignore') as f:
                    contents[filepath] = f.read()
            else:
                contents[filepath] = f"[File not found: {filepath}]"
        except Exception as e:
            contents[filepath] = f"[Error reading {filepath}: {e}]"
    return contents


@router.post("/dispatch")
async def dispatch_job(req: DispatchRequest) -> dict[str, Any]:
    """Dispatch a tool to work on a ticket. Enqueues a job."""
    tool = ToolRegistry.get().get_tool(req.tool)
    if not tool:
        raise HTTPException(404, f"Tool '{req.tool}' not found")

    ticket = _get_ticket(req.ticket_id)
    if tool.name == "human":
        from proxym.dashboard._tickets_api import _tickets
        from proxym.dashboard.tickets import ToolAssignment

        mgr = _tickets()
        instance_name = req.instance or ""
        mgr.assign_tool(ticket.id, ToolAssignment(tool=tool.name, agent_mode=req.mode, model=req.model, instance=instance_name))
        updated = mgr.get_ticket(ticket.id)
        return {
            "job": None,
            "ticket": updated.summary() if updated else ticket.summary(),
            "message": f"Human task assigned for ticket {ticket.id}",
        }

    # Idempotency: skip if ticket already has a queued or running job
    from proxym.tools.executor import ToolExecutor
    executor = ToolExecutor.get()
    existing_jobs = executor.get_jobs_for_ticket(req.ticket_id)
    for ej in existing_jobs:
        if ej.status.value in ("queued", "running"):
            return {"job": ej.summary(), "message": f"Job {ej.id} already active for ticket {req.ticket_id}"}

    environment = get_tool_environment_snapshot(req.tool)
    # Priority: explicit request > ticket's project > tool's environment > fallback
    project_dir = req.project_dir or _default_project_dir(ticket.project_id) or (environment.get("path") if environment else "")

    # Get instance from request or environment, and update environment if request specifies one
    if req.instance and environment:
        environment = dict(environment)
        environment["tool_instance_name"] = req.instance

    # Make dispatch self-contained: if the worker isn't running yet, start it
    # before enqueueing so the job doesn't stay queued forever.
    executor.start()
    # Resolve model alias before enqueueing
    from proxym.tools.adapters._base import _resolve_model_for_tool
    resolved_model = _resolve_model_for_tool(req.model or tool.default_model, tool.name)
    job = executor.enqueue(ticket, tool, project_dir, req.mode, resolved_model, environment=environment)

    try:
        from proxym.domain.setup import get_event_store
        store = get_event_store()
        store.append(JobDispatched(
            aggregate_id=job.id,
            actor="api",
            data={"ticket_id": req.ticket_id, "tool": tool.name, "mode": req.mode, "model": req.model},
        ))
        store.append(TaskAssigned(
            aggregate_id=req.ticket_id,
            actor="api",
            data={"tool": tool.name, "job_id": job.id},
        ))
    except Exception:
        pass

    return {"job": job.summary(), "message": f"Job {job.id} enqueued for {tool.name}"}
