"""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.storage import DEFAULT_SQLITE_PATH
from proxym.tools.executor import ToolExecutor
from proxym.dashboard._environments_api import get_tool_environment_snapshot
from proxym.dashboard.tickets import (
    Milestone,
    MilestoneStatus,
    Sprint,
    SprintStatus,
    Ticket,
    TicketComment,
    TicketManager,
    TicketPriority,
    TicketStatus,
    TicketType,
    ToolAssignment,
)
from proxym.dashboard.tools._helpers import _default_project_dir, _job_snapshot

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

TICKET_NOT_FOUND = "Ticket not found"
SPRINT_NOT_FOUND = "Sprint not found"

# Singleton
_ticket_mgr: TicketManager | None = None


def _tickets() -> TicketManager:
    global _ticket_mgr
    if _ticket_mgr is None:
        persist = DEFAULT_SQLITE_PATH
        _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 = ""
    milestone_id: str = ""
    start_date: str = ""
    end_date: str = ""


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


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


class UpdateMilestoneReq(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,
    milestone_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, milestone_id=milestone_id, status=st, priority=pr, tool=tool, tag=tag)
    return {"tickets": [mgr.ticket_overview(t) for t in tickets]}


@router.get("/tickets/export")
async def export_tickets(
    sprint_id: str | None = None,
    milestone_id: str | None = None,
    status: str | None = None,
    priority: str | None = None,
    tool: str | None = None,
    tag: str | None = None,
    view: str = "list",
    detail: bool = True,
):
    """Export ticket data for grouped dashboard views."""
    mgr = _tickets()
    st = TicketStatus(status) if status else None
    pr = TicketPriority(priority) if priority else None
    tickets = mgr.list_tickets(sprint_id=sprint_id, milestone_id=milestone_id, status=st, priority=pr, tool=tool, tag=tag)
    rows = []
    for ticket in tickets:
        row = mgr.ticket_detail(ticket) if detail else mgr.ticket_overview(ticket)
        if detail:
            row["job_history"] = [_job_snapshot(job) for job in ToolExecutor.get().get_jobs_for_ticket(ticket.id)]
        rows.append(row)
    if view == "board":
        for row in rows:
            row["board_column"] = row.get("status", "")
    return {
        "tickets": rows,
        "count": len(rows),
        "view": view,
        "detail": detail,
        "filters": {
            "sprint_id": sprint_id,
            "milestone_id": milestone_id,
            "status": status,
            "priority": priority,
            "tool": tool,
            "tag": tag,
        },
    }


@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(""),
    milestone_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, instance="")
        mgr.assign_tool(created.id, assignment)

        tool_def = None
        try:
            from proxym.tools import ToolRegistry
            tool_def = ToolRegistry.get().get_tool(tool)
        except Exception:
            tool_def = None

        if tool_def and tool_def.name == "human":
            return mgr.get_ticket(created.id).summary()

        # Auto-dispatch: enqueue job immediately so user doesn't need a second step
        try:
            from proxym.tools.executor import ToolExecutor
            from proxym.dashboard.tickets import JobExecution
            if tool_def:
                executor = ToolExecutor.get()
                environment_snapshot = get_tool_environment_snapshot(tool_def.name)
                project_dir = (environment_snapshot.get("path") if environment_snapshot else "") or _default_project_dir(project_id)
                job = executor.enqueue(created, tool_def, project_dir, environment=environment_snapshot)
                # Store execution snapshot on the ticket (persists to JSON)
                created.last_execution = JobExecution(
                    job_id=job.id,
                    tool=job.tool_name,
                    mode=job.mode,
                    model=job.model,
                    project_dir=job.project_dir,
                    environment_id=environment_snapshot.get("id", "") if environment_snapshot else "",
                    environment_name=environment_snapshot.get("name", "") if environment_snapshot else "",
                    environment_kind=environment_snapshot.get("kind", "") if environment_snapshot else "",
                    environment_target=environment_snapshot.get("target", "") if environment_snapshot else "",
                    environment_tool_instance_name=environment_snapshot.get("tool_instance_name", "") if environment_snapshot else "",
                    environment_tool_instance=environment_snapshot.get("tool_instance", {}) if environment_snapshot else {},
                    prompt_excerpt=job.prompt[:400].strip(),
                    prompt_length=len(job.prompt),
                    status=job.status.value,
                    success=None,
                    exit_code=None,
                )
                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()
@router.get("/tickets/board/view")
async def ticket_board(sprint_id: str | None = None, milestone_id: str | None = None):
    """Kanban board view: tickets grouped by status columns."""
    return _tickets().board(sprint_id=sprint_id, milestone_id=milestone_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 = _tickets().ticket_detail(ticket)
    result["job_history"] = [_job_snapshot(job) for job in ToolExecutor.get().get_jobs_for_ticket(ticket_id)]
    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),
    milestone_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."""
    from proxym.dashboard.utils.delete_ticket import delete_item
    return delete_item(_tickets().delete_ticket, ticket_id, 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(""),
    instance: 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, instance=instance)
    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, milestone_id: str | None = None):
    """List sprints."""
    st = SprintStatus(status) if status else None
    sprints = _tickets().list_sprints(status=st, milestone_id=milestone_id)
    mgr = _tickets()
    return {"sprints": [mgr.sprint_overview(s) 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,
        milestone_id=req.milestone_id,
        start_date=req.start_date,
        end_date=req.end_date,
    )
    created = _tickets().create_sprint(sprint)
    return _tickets().sprint_overview(created)


@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 = mgr.sprint_overview(sprint)
    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 mgr.sprint_overview(sprint)


@router.delete("/sprints/{sprint_id}")
async def delete_sprint(sprint_id: str):
    """Delete a sprint."""
    from proxym.dashboard.utils.delete_ticket import delete_item
    return delete_item(_tickets().delete_sprint, sprint_id, SPRINT_NOT_FOUND)


# ── Milestone endpoints ──────────────────────────────────────

@router.get("/milestones")
async def list_milestones(status: str | None = None):
    """List milestones."""
    st = MilestoneStatus(status) if status else None
    mgr = _tickets()
    milestones = mgr.list_milestones(status=st)
    return {"milestones": [mgr.milestone_overview(m) for m in milestones]}


@router.post("/milestones")
async def create_milestone(req: CreateMilestoneReq):
    """Create a new milestone."""
    milestone = Milestone(
        name=req.name,
        goal=req.goal,
        start_date=req.start_date,
        end_date=req.end_date,
    )
    created = _tickets().create_milestone(milestone)
    return _tickets().milestone_overview(created)


@router.get("/milestones/{milestone_id}")
async def get_milestone(milestone_id: str):
    """Get milestone details with ticket stats."""
    mgr = _tickets()
    milestone = mgr.get_milestone(milestone_id)
    if not milestone:
        raise HTTPException(404, "Milestone not found")
    result = mgr.milestone_overview(milestone)
    result["stats"] = mgr.milestone_stats(milestone_id)
    return result


@router.put("/milestones/{milestone_id}")
async def update_milestone(milestone_id: str, req: UpdateMilestoneReq):
    """Update milestone fields."""
    updates = {k: v for k, v in req.model_dump().items() if v is not None}
    milestone = _tickets().update_milestone(milestone_id, updates)
    if not milestone:
        raise HTTPException(404, "Milestone not found")
    mgr = _tickets()
    return mgr.milestone_overview(milestone)


@router.delete("/milestones/{milestone_id}")
async def delete_milestone(milestone_id: str):
    """Delete a milestone."""
    from proxym.dashboard.utils.delete_milestone import delete_milestone as delete_milestone_util
    return delete_milestone_util(_tickets().delete_milestone, milestone_id)
