import logging

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from backend.app.core.auth import check_permission, check_printer_access, get_api_key
from backend.app.core.database import get_db
from backend.app.models.api_key import APIKey
from backend.app.models.archive import PrintArchive
from backend.app.models.print_queue import PrintQueueItem
from backend.app.models.printer import Printer
from backend.app.services.printer_manager import printer_manager

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/webhook", tags=["webhook"])


# Request schemas
class QueueAddRequest(BaseModel):
    archive_id: int
    printer_id: int
    project_id: int | None = None
    scheduled_time: str | None = None  # ISO format datetime
    require_previous_success: bool = False
    auto_off_after: bool = False


class QueueAddResponse(BaseModel):
    id: int
    archive_id: int
    printer_id: int
    position: int
    status: str
    message: str


class PrinterStatusResponse(BaseModel):
    id: int
    name: str
    connected: bool
    state: str | None
    current_print: str | None
    progress: float | None
    remaining_time: int | None


class QueueStatusResponse(BaseModel):
    printer_id: int
    printer_name: str
    pending: int
    printing: int
    items: list[dict]


# Webhook endpoints


@router.post("/queue/add", response_model=QueueAddResponse)
async def webhook_add_to_queue(
    data: QueueAddRequest,
    api_key: APIKey = Depends(get_api_key),
    db: AsyncSession = Depends(get_db),
):
    """Add a print to the queue via webhook.

    Requires 'can_queue' permission.
    """
    check_permission(api_key, "queue")
    check_printer_access(api_key, data.printer_id)

    # Verify archive exists
    result = await db.execute(select(PrintArchive).where(PrintArchive.id == data.archive_id))
    archive = result.scalar_one_or_none()
    if not archive:
        raise HTTPException(status_code=404, detail="Archive not found")

    # Verify printer exists
    result = await db.execute(select(Printer).where(Printer.id == data.printer_id))
    printer = result.scalar_one_or_none()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")

    # Get next position
    result = await db.execute(
        select(PrintQueueItem.position)
        .where(
            PrintQueueItem.printer_id == data.printer_id,
            PrintQueueItem.status == "pending",
        )
        .order_by(PrintQueueItem.position.desc())
        .limit(1)
    )
    max_position = result.scalar()
    next_position = (max_position or 0) + 1

    # Parse scheduled time if provided
    scheduled_time = None
    if data.scheduled_time:
        from datetime import datetime

        try:
            scheduled_time = datetime.fromisoformat(data.scheduled_time.replace("Z", "+00:00"))
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid scheduled_time format")

    # Create queue item
    queue_item = PrintQueueItem(
        printer_id=data.printer_id,
        archive_id=data.archive_id,
        project_id=data.project_id,
        position=next_position,
        scheduled_time=scheduled_time,
        require_previous_success=data.require_previous_success,
        auto_off_after=data.auto_off_after,
    )
    db.add(queue_item)
    await db.flush()
    await db.refresh(queue_item)

    return QueueAddResponse(
        id=queue_item.id,
        archive_id=queue_item.archive_id,
        printer_id=queue_item.printer_id,
        position=queue_item.position,
        status=queue_item.status,
        message=f"Added to queue at position {queue_item.position}",
    )


@router.post("/printer/{printer_id}/start")
async def webhook_start_print(
    printer_id: int,
    api_key: APIKey = Depends(get_api_key),
    db: AsyncSession = Depends(get_db),
):
    """Trigger the next manual-start queue item on a printer.

    Mirrors `POST /print-queue/{item_id}/start`: clears `manual_start` on
    the next pending item so the scheduler picks it up — which handles
    FTP upload, AMS mapping, and all print options (timelapse,
    bed_levelling, etc.) correctly via the queue's stored fields. The
    previous implementation called `printer_manager.start_print()`
    directly with `archive_id` as the filename arg and no print options,
    bypassing the upload step entirely and discarding the user's
    workflow choices — it 500'd before ever reaching the printer.

    Requires 'can_control_printer' permission.
    """
    check_permission(api_key, "control_printer")
    check_printer_access(api_key, printer_id)

    # Get printer
    result = await db.execute(select(Printer).where(Printer.id == printer_id))
    printer = result.scalar_one_or_none()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")

    # Get next pending queue item
    result = await db.execute(
        select(PrintQueueItem)
        .where(
            PrintQueueItem.printer_id == printer_id,
            PrintQueueItem.status == "pending",
        )
        .order_by(PrintQueueItem.position)
        .limit(1)
    )
    queue_item = result.scalar_one_or_none()
    if not queue_item:
        raise HTTPException(status_code=404, detail="No pending prints in queue")

    # Clear manual_start so the scheduler will dispatch. If the item was
    # already auto-dispatchable this is a no-op; the scheduler will still
    # pick it up on its next tick.
    queue_item.manual_start = False
    await db.commit()
    await db.refresh(queue_item)

    logger.info("Webhook started queue item %s on printer %s", queue_item.id, printer_id)
    return {"message": "Print started", "queue_item_id": queue_item.id}


@router.post("/printer/{printer_id}/stop")
async def webhook_stop_print(
    printer_id: int,
    api_key: APIKey = Depends(get_api_key),
):
    """Stop the current print on a printer.

    Requires 'can_control_printer' permission.
    """
    check_permission(api_key, "control_printer")
    check_printer_access(api_key, printer_id)

    status = printer_manager.get_status(printer_id)
    if not status or not status.get("connected"):
        raise HTTPException(status_code=503, detail="Printer not connected")

    if status.get("state") != "RUNNING":
        raise HTTPException(status_code=409, detail="No print in progress")

    try:
        await printer_manager.stop_print(printer_id)
    except Exception as e:
        logger.error("Failed to stop print: %s", e)
        raise HTTPException(status_code=500, detail=str(e))

    return {"message": "Print stopped"}


@router.post("/printer/{printer_id}/cancel")
async def webhook_cancel_print(
    printer_id: int,
    api_key: APIKey = Depends(get_api_key),
):
    """Cancel the current print on a printer.

    Requires 'can_control_printer' permission.
    """
    check_permission(api_key, "control_printer")
    check_printer_access(api_key, printer_id)

    status = printer_manager.get_status(printer_id)
    if not status or not status.get("connected"):
        raise HTTPException(status_code=503, detail="Printer not connected")

    if status.get("state") not in ["RUNNING", "PAUSE"]:
        raise HTTPException(status_code=409, detail="No print to cancel")

    try:
        await printer_manager.cancel_print(printer_id)
    except Exception as e:
        logger.error("Failed to cancel print: %s", e)
        raise HTTPException(status_code=500, detail=str(e))

    return {"message": "Print cancelled"}


@router.get("/printer/{printer_id}/status", response_model=PrinterStatusResponse)
async def webhook_get_printer_status(
    printer_id: int,
    api_key: APIKey = Depends(get_api_key),
    db: AsyncSession = Depends(get_db),
):
    """Get status of a printer.

    Requires 'can_read_status' permission.
    """
    check_permission(api_key, "read_status")
    check_printer_access(api_key, printer_id)

    # Get printer
    result = await db.execute(select(Printer).where(Printer.id == printer_id))
    printer = result.scalar_one_or_none()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")

    status = printer_manager.get_status(printer_id)

    return PrinterStatusResponse(
        id=printer.id,
        name=printer.name,
        connected=status.get("connected", False) if status else False,
        state=status.get("state") if status else None,
        current_print=status.get("current_print") if status else None,
        progress=status.get("progress") if status else None,
        remaining_time=status.get("remaining_time") if status else None,
    )


@router.get("/queue", response_model=list[QueueStatusResponse])
async def webhook_get_queue_status(
    printer_id: int | None = None,
    api_key: APIKey = Depends(get_api_key),
    db: AsyncSession = Depends(get_db),
):
    """Get queue status for all printers or a specific printer.

    Requires 'can_read_status' permission.
    """
    check_permission(api_key, "read_status")

    # Get printers
    if printer_id:
        check_printer_access(api_key, printer_id)
        result = await db.execute(select(Printer).where(Printer.id == printer_id))
        printers = result.scalars().all()
    else:
        result = await db.execute(select(Printer))
        printers = result.scalars().all()
        # Filter by allowed printers if limited
        if api_key.printer_ids is not None:
            printers = [p for p in printers if p.id in api_key.printer_ids]

    response = []
    for printer in printers:
        # Get queue items
        result = await db.execute(
            select(PrintQueueItem)
            .where(
                PrintQueueItem.printer_id == printer.id,
                PrintQueueItem.status.in_(["pending", "printing"]),
            )
            .order_by(PrintQueueItem.position)
        )
        items = result.scalars().all()

        pending_count = sum(1 for i in items if i.status == "pending")
        printing_count = sum(1 for i in items if i.status == "printing")

        response.append(
            QueueStatusResponse(
                printer_id=printer.id,
                printer_name=printer.name,
                pending=pending_count,
                printing=printing_count,
                items=[
                    {
                        "id": item.id,
                        "archive_id": item.archive_id,
                        "position": item.position,
                        "status": item.status,
                    }
                    for item in items
                ],
            )
        )

    return response
