"""Tickets and sprints endpoints for dashboard API."""
from __future__ import annotations

from pathlib import Path
from typing import Annotated

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

from proxym.observability import log_call
from proxym.dashboard.tickets import (
    Sprint,
    SprintStatus,
    Ticket,
    TicketComment,
    TicketManager,
    TicketPriority,
    TicketStatus,
    TicketType,
    ToolAssignment,
)

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

# Singleton
_ticket_mgr: TicketManager | None = None


def _tickets() -> TicketManager:
    global _ticket_mgr
    if _ticket_mgr is None:
        persist = Path.home() / ".local" / "share" / "proxym" / "tickets.json"
        _ticket_mgr = TicketManager(persist_path=persist)
    return _ticket_mgr


# ── Pydantic models ──────────────────────────────────────────

class CreateTicketReq(BaseModel):
    task: str = ""                                   # "co zrobić" — nowe główne pole
    title: str = ""                                  # backward compat
    project_id: str | None = None                    # powiązanie z projektem
    description: str = ""
    status: str = "todo"
    priority: str = "medium"
    type: str = "task"
    sprint_id: str = ""
    tags: list[str] = []
    files: list[str] = []
    estimated_hours: float = 0
    tool: str | None = None                          # od razu przypisz narzędzie


class UpdateTicketReq(BaseModel):
    task: str | None = None
    title: str | None = None
    project_id: str | None = None
    description: str | None = None
    status: str | None = None
    priority: str | None = None
    type: str | None = None
    sprint_id: str | None = None
    tags: list[str] | None = None
    files: list[str] | None = None
    estimated_hours: float | None = None
    actual_hours: float | None = None


class AssignToolReq(BaseModel):
    tool: str  # vscode, aider, claude-code, windsurf, cursor
    agent_mode: str = ""
    model: str = ""


class AddCommentReq(BaseModel):
    author: str = ""
    content: str


class CreateSprintReq(BaseModel):
    name: str
    goal: str = ""
    start_date: str = ""
    end_date: str = ""


class UpdateSprintReq(BaseModel):
    name: str | None = None
    goal: str | None = None
    status: str | None = None
    start_date: str | None = None
    end_date: str | None = None


# ── Ticket endpoints ─────────────────────────────────────────

@router.get("/tickets")
async def list_tickets(
    sprint_id: str | None = None,
    status: str | None = None,
    priority: str | None = None,
    tool: str | None = None,
    tag: str | None = None,
):
    """List tickets with optional filters."""
    mgr = _tickets()
    st = TicketStatus(status) if status else None
    pr = TicketPriority(priority) if priority else None
    tickets = mgr.list_tickets(sprint_id=sprint_id, status=st, priority=pr, tool=tool, tag=tag)
    return {"tickets": [t.summary() for t in tickets]}


@router.post("/tickets")
@log_call()
async def create_ticket(
    task: str = Body(""),
    title: str = Body(""),
    project_id: str | None = Body(None),
    description: str = Body(""),
    status: str = Body("todo"),
    priority: str = Body("medium"),
    type: str = Body("task"),
    sprint_id: str = Body(""),
    tags: list[str] = Body(default_factory=list),
    files: list[str] = Body(default_factory=list),
    estimated_hours: float = Body(0),
    tool: str | None = Body(None),
):
    """Create a new ticket. Accepts 'task' (new) or 'title' (legacy)."""
    task_text = task or title
    if not task_text:
        from fastapi import HTTPException
        raise HTTPException(400, "Provide 'task' or 'title'")
    ticket = Ticket(
        task=task_text,
        title=title,  # auto-generated from task if empty
        project_id=project_id,
        description=description,
        status=TicketStatus(status),
        priority=TicketPriority(priority),
        type=TicketType(type),
        sprint_id=sprint_id,
        tags=tags,
        files=files,
        estimated_hours=estimated_hours,
    )
    mgr = _tickets()
    created = mgr.create_ticket(ticket)

    # Od razu przypisz narzędzie jeśli podane
    if tool:
        assignment = ToolAssignment(tool=tool)
        mgr.assign_tool(created.id, assignment)

        # Auto-dispatch: enqueue job immediately so user doesn't need a second step
        try:
            from proxym.tools import ToolRegistry
            from proxym.tools.executor import ToolExecutor
            from proxym.dashboard.tickets import JobExecution
            tool_def = ToolRegistry.get().get_tool(tool)
            if tool_def:
                executor = ToolExecutor.get()
                job = executor.enqueue(created, tool_def, _default_project_dir(project_id))
                # Store execution snapshot on the ticket (persists to JSON)
                created.last_execution = JobExecution(
                    job_id=job.id,
                    tool=job.tool_name,
                    status=job.status.value,
                )
                mgr.update_ticket(created.id, {"last_execution": created.last_execution})
        except Exception as exc:
            import structlog
            structlog.get_logger().warning("auto_dispatch_failed", ticket=created.id, error=str(exc))

    return created.summary()


def _default_project_dir(project_id: str | None = None) -> str:
    """Return project directory for dispatch."""
    if project_id:
        try:
            from proxym.dashboard._projects_api import _get_registry
            p = _get_registry().get(project_id)
            if p and p.local_path:
                return p.local_path
        except Exception:
            pass
    try:
        from proxym.config import get_settings
        return str(getattr(get_settings(), "projects_root", "~/github"))
    except Exception:
        return "~/github"


@router.get("/tickets/board/view")
async def ticket_board(sprint_id: str | None = None):
    """Kanban board view: tickets grouped by status columns."""
    return _tickets().board(sprint_id=sprint_id)


@router.get("/tickets/{ticket_id}")
async def get_ticket(ticket_id: str):
    """Get ticket details including comments."""
    ticket = _tickets().get_ticket(ticket_id)
    if not ticket:
        raise HTTPException(404, "Ticket not found")
    result = ticket.summary()
    result["comments"] = [c.model_dump() for c in ticket.comments]
    return result


@router.put("/tickets/{ticket_id}")
async def update_ticket(
    ticket_id: str,
    task: str | None = Body(None),
    title: str | None = Body(None),
    project_id: str | None = Body(None),
    description: str | None = Body(None),
    status: str | None = Body(None),
    priority: str | None = Body(None),
    type: str | None = Body(None),
    sprint_id: str | None = Body(None),
    tags: list[str] | None = Body(None),
    files: list[str] | None = Body(None),
    estimated_hours: float | None = Body(None),
    actual_hours: float | None = Body(None),
):
    """Update a ticket's fields."""
    updates = {k: v for k, v in {
        "task": task, "title": title, "project_id": project_id,
        "description": description, "status": status, "priority": priority,
        "type": type, "sprint_id": sprint_id, "tags": tags,
        "files": files, "estimated_hours": estimated_hours, "actual_hours": actual_hours,
    }.items() if v is not None}
    ticket = _tickets().update_ticket(ticket_id, updates)
    if not ticket:
        raise HTTPException(404, "Ticket not found")
    return ticket.summary()


@router.delete("/tickets/{ticket_id}")
async def delete_ticket(ticket_id: str):
    """Delete a ticket."""
    if _tickets().delete_ticket(ticket_id):
        return {"ok": True}
    raise HTTPException(404, "Ticket not found")


@router.post("/tickets/{ticket_id}/assign")
@log_call()
async def assign_ticket_tool(
    ticket_id: str,
    tool: str = Body(...),
    agent_mode: str = Body(""),
    model: str = Body(""),
):
    """Assign a tool (vscode, aider, claude-code, etc.) to work on a ticket."""
    assignment = ToolAssignment(tool=tool, agent_mode=agent_mode, model=model)
    if _tickets().assign_tool(ticket_id, assignment):
        return {"ok": True, "ticket": _tickets().get_ticket(ticket_id).summary()}
    raise HTTPException(404, "Ticket not found")


@router.post("/tickets/{ticket_id}/comment")
async def add_ticket_comment(
    ticket_id: str,
    author: str = Body(""),
    content: str = Body(...),
):
    """Add a comment to a ticket (from any tool or user)."""
    comment = TicketComment(author=author, content=content)
    if _tickets().add_comment(ticket_id, comment):
        return {"ok": True}
    raise HTTPException(404, "Ticket not found")


# ── Sprint endpoints ─────────────────────────────────────────

@router.get("/sprints")
async def list_sprints(status: str | None = None):
    """List sprints."""
    st = SprintStatus(status) if status else None
    sprints = _tickets().list_sprints(status=st)
    mgr = _tickets()
    return {"sprints": [s.summary(mgr.sprint_tickets(s.id)) for s in sprints]}


@router.post("/sprints")
async def create_sprint(req: CreateSprintReq):
    """Create a new sprint."""
    sprint = Sprint(
        name=req.name,
        goal=req.goal,
        start_date=req.start_date,
        end_date=req.end_date,
    )
    created = _tickets().create_sprint(sprint)
    return created.summary()


@router.get("/sprints/{sprint_id}")
async def get_sprint(sprint_id: str):
    """Get sprint details with ticket stats."""
    mgr = _tickets()
    sprint = mgr.get_sprint(sprint_id)
    if not sprint:
        raise HTTPException(404, "Sprint not found")
    result = sprint.summary(mgr.sprint_tickets(sprint_id))
    result["stats"] = mgr.sprint_stats(sprint_id)
    return result


@router.put("/sprints/{sprint_id}")
async def update_sprint(sprint_id: str, req: UpdateSprintReq):
    """Update sprint fields."""
    updates = {k: v for k, v in req.model_dump().items() if v is not None}
    sprint = _tickets().update_sprint(sprint_id, updates)
    if not sprint:
        raise HTTPException(404, "Sprint not found")
    mgr = _tickets()
    return sprint.summary(mgr.sprint_tickets(sprint_id))


@router.delete("/sprints/{sprint_id}")
async def delete_sprint(sprint_id: str):
    """Delete a sprint."""
    if _tickets().delete_sprint(sprint_id):
        return {"ok": True}
    raise HTTPException(404, "Sprint not found")
