"""Tickets and sprints endpoints for dashboard API."""

from __future__ import annotations

import structlog

from fastapi import APIRouter, Body, HTTPException

from proxym.dashboard._environments_api import get_tool_environment_snapshot
from proxym.dashboard._tickets_models import (
    AddCommentReq,
    AssignToolReq,
    CreateMilestoneReq,
    CreateSprintReq,
    CreateTicketReq,
    UpdateMilestoneReq,
    UpdateSprintReq,
    UpdateTicketReq,
)
from proxym.dashboard._tickets_support import (
    build_ticket_detail as _build_ticket_detail_impl,
    build_ticket_export_rows as _build_ticket_export_rows_impl,
    enqueue_ticket_job as _enqueue_ticket_job_impl,
    maybe_auto_dispatch_ticket as _maybe_auto_dispatch_ticket_impl,
)
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
from proxym.dashboard.utils._emit_event import emit_event as _emit_event
from proxym.domain.events import (
    TaskAssigned,
    TaskCreated,
    TaskDeleted,
    TaskUpdated,
)
from proxym.observability import log_call
from proxym.storage import DEFAULT_SQLITE_PATH
from proxym.tools import ToolRegistry
from proxym.tools.executor import ToolExecutor

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


__all__ = [
    "AddCommentReq",
    "AssignToolReq",
    "CreateMilestoneReq",
    "CreateSprintReq",
    "CreateTicketReq",
    "TICKET_NOT_FOUND",
    "SPRINT_NOT_FOUND",
    "UpdateMilestoneReq",
    "UpdateSprintReq",
    "UpdateTicketReq",
    "_tickets",
    "router",
]


# ── 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,
    project_id: str | None = None,
):
    """List tickets with optional filters."""
    mgr = _tickets()
    st = TicketStatus(status) if status else None
    pr = TicketPriority(priority) if priority else None
    # Support comma-separated project_id for multi-project filtering
    resolved_pid: str | set[str] | None = None
    if project_id:
        parts = [p.strip() for p in project_id.split(",") if p.strip()]
        resolved_pid = set(parts) if len(parts) > 1 else (parts[0] if parts else None)
    tickets = mgr.list_tickets(
        sprint_id=sprint_id, milestone_id=milestone_id, status=st, priority=pr, tool=tool, tag=tag,
        project_id=resolved_pid,
    )
    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,
    project_id: str | None = None,
    project_name: 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
    # Resolve project_name to project_id(s) — may match multiple projects with same name
    resolved_project_id: str | set[str] | None = project_id
    if not resolved_project_id and project_name:
        try:
            from proxym.dashboard._projects_api import _get_registry
            registry = _get_registry()
            matching_ids = {p.id for p in registry.list() if p.name == project_name or p.id == project_name}
            if matching_ids:
                resolved_project_id = matching_ids if len(matching_ids) > 1 else next(iter(matching_ids))
        except Exception:
            pass
    tickets = mgr.list_tickets(
        sprint_id=sprint_id, milestone_id=milestone_id, status=st, priority=pr, tool=tool, tag=tag,
        project_id=resolved_project_id,
    )
    rows = _build_ticket_export_rows_impl(
        mgr,
        tickets,
        detail=detail,
        view=view,
        tool_executor_get=ToolExecutor.get,
    )
    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,
            "project_id": resolved_project_id,
            "project_name": project_name,
        },
    }


@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),
    context_files: list[str] = Body(default_factory=list),
    depends_on: 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:
        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,
        milestone_id=milestone_id,
        tags=tags,
        files=files,
        context_files=context_files,
        depends_on=depends_on,
        estimated_hours=estimated_hours,
    )
    mgr = _tickets()
    created = mgr.create_ticket(ticket)

    _emit_event(
        TaskCreated(
            aggregate_id=created.id,
            actor="api",
            data={"task": task_text, "project_id": project_id or "", "priority": priority},
        )
    )

    # 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:
            if tool_def:
                logger = structlog.get_logger()
                logger.info("auto_dispatch_debug", 
                           enqueue_impl=_enqueue_ticket_job_impl,
                           is_none=_enqueue_ticket_job_impl is None,
                           tool_def=tool_def,
                           tool_executor_get=ToolExecutor.get)
                _, execution = await _enqueue_ticket_job_impl(
                    created,
                    tool_def,
                    ticket_store=mgr,
                    tool_executor_get=ToolExecutor.get,
                    get_tool_environment_snapshot=get_tool_environment_snapshot,
                    default_project_dir=_default_project_dir,
                )
                created.last_execution = execution
        except Exception as exc:
            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.post("/tickets/dedup")
@log_call()
async def deduplicate_tickets(dry_run: bool = Body(True, embed=True)):
    """Find and remove duplicate tickets.

    Duplicates are tickets with identical (task, project_id, type).
    The ticket with the most activity (comments, tool assignment, latest
    update) is kept; the rest are removed.

    Set ``dry_run=false`` to actually delete the duplicates.
    """
    result = _tickets().find_duplicates(dry_run=dry_run)
    return result


@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)
    return _build_ticket_detail_impl(_tickets(), ticket, ToolExecutor.get)


@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),
    context_files: list[str] | None = Body(None),
    depends_on: list[str] | None = Body(None),
    estimated_hours: float | None = Body(None),
    actual_hours: float | None = Body(None),
):
    """Update a ticket's fields."""
    existing = _tickets().get_ticket(ticket_id)
    previous_status = existing.status if existing else None
    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,
            "milestone_id": milestone_id,
            "tags": tags,
            "files": files,
            "context_files": context_files,
            "depends_on": depends_on,
            "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)

    _emit_event(
        TaskUpdated(
            aggregate_id=ticket_id,
            actor="api",
            data={
                **{k: str(v) for k, v in updates.items()},
                "previous_status": previous_status.value if previous_status else "",
                "current_status": ticket.status.value,
            },
        )
    )

    if previous_status != TicketStatus.IN_PROGRESS and ticket.status == TicketStatus.IN_PROGRESS:
        await _maybe_auto_dispatch_ticket_impl(
            ticket_id,
            ticket_store=_tickets(),
            tool_registry_get=ToolRegistry.get,
            tool_executor_get=ToolExecutor.get,
            get_tool_environment_snapshot=get_tool_environment_snapshot,
            default_project_dir=_default_project_dir,
        )

    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

    result = delete_item(_tickets().delete_ticket, ticket_id, TICKET_NOT_FOUND)
    _emit_event(TaskDeleted(aggregate_id=ticket_id, actor="api"))
    return result


@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."""
    import structlog
    logger = structlog.get_logger()
    logger.info("assign_tool_endpoint", ticket_id=ticket_id, tool=tool)
    assignment = ToolAssignment(tool=tool, agent_mode=agent_mode, model=model, instance=instance)
    existing = _tickets().get_ticket(ticket_id)
    if not existing:
        raise HTTPException(404, TICKET_NOT_FOUND)
    if _tickets().assign_tool(ticket_id, assignment):
        logger.info("assign_tool_success", ticket_id=ticket_id, new_status=existing.status.value if existing else None)
        _emit_event(
            TaskAssigned(
                aggregate_id=ticket_id,
                actor="api",
                data={"tool": tool, "agent_mode": agent_mode, "model": model},
            )
        )
        # Auto-dispatch job for non-human tools
        if tool != "human":
            logger.info("assign_tool_dispatching", ticket_id=ticket_id, tool=tool)
            await _maybe_auto_dispatch_ticket_impl(
                ticket_id,
                ticket_store=_tickets(),
                tool_registry_get=ToolRegistry.get,
                tool_executor_get=ToolExecutor.get,
                get_tool_environment_snapshot=get_tool_environment_snapshot,
                default_project_dir=_default_project_dir,
            )
        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)
