"""Output Types API router."""
import uuid

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select, or_, cast, String
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from sqlalchemy.dialects.postgresql import JSONB

from app.database import get_db
from app.models.order_line import OrderLine
from app.models.output_type import OutputType, VALID_RENDER_BACKENDS
from app.schemas.output_type import OutputTypeCreate, OutputTypeOut, OutputTypePatch
from app.utils.auth import get_current_user, require_admin_or_pm
from app.models.user import User
from app.domains.rendering.models import WorkflowDefinition

router = APIRouter(prefix="/output-types", tags=["output-types"])


def _ot_to_out(ot: OutputType) -> OutputTypeOut:
    """Convert an OutputType ORM instance to OutputTypeOut with pricing convenience fields."""
    out = OutputTypeOut.model_validate(ot)
    if ot.pricing_tier:
        out.pricing_tier_name = f"{ot.pricing_tier.category_key}/{ot.pricing_tier.quality_level}"
        out.price_per_item = float(ot.pricing_tier.price_per_item)
    # workflow_definition_id is mapped via model_validate from the ORM column.
    # workflow_name is resolved by _enrich_workflow_names() after the fact.
    return out


async def _enrich_workflow_names(db: AsyncSession, items: list[OutputTypeOut]) -> list[OutputTypeOut]:
    """Resolve workflow_name for any OutputTypeOut that has a workflow_definition_id set."""
    wf_ids = {item.workflow_definition_id for item in items if item.workflow_definition_id is not None}
    if not wf_ids:
        return items
    wf_result = await db.execute(
        select(WorkflowDefinition).where(WorkflowDefinition.id.in_(wf_ids))
    )
    wf_map: dict[uuid.UUID, str] = {wf.id: wf.name for wf in wf_result.scalars().all()}
    for item in items:
        if item.workflow_definition_id is not None:
            item.workflow_name = wf_map.get(item.workflow_definition_id)
    return items


@router.get("", response_model=list[OutputTypeOut])
async def list_output_types(
    include_inactive: bool = Query(False),
    category: str = Query(""),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    stmt = (
        select(OutputType)
        .options(selectinload(OutputType.pricing_tier))
        .order_by(OutputType.sort_order, OutputType.name)
    )
    if not include_inactive:
        stmt = stmt.where(OutputType.is_active.is_(True))
    if category:
        # Show output types where compatible_categories is empty (universal)
        # or contains the given category
        stmt = stmt.where(
            or_(
                cast(OutputType.compatible_categories, String) == "[]",
                OutputType.compatible_categories.contains([category]),
            )
        )
    result = await db.execute(stmt)
    items = [_ot_to_out(ot) for ot in result.scalars().all()]
    return await _enrich_workflow_names(db, items)


@router.post("", response_model=OutputTypeOut, status_code=status.HTTP_201_CREATED)
async def create_output_type(
    body: OutputTypeCreate,
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    if body.render_backend not in VALID_RENDER_BACKENDS:
        raise HTTPException(400, detail=f"Invalid render_backend. Choose: {', '.join(sorted(VALID_RENDER_BACKENDS))}")

    existing = await db.execute(select(OutputType).where(OutputType.name == body.name))
    if existing.scalar_one_or_none():
        raise HTTPException(409, detail=f"Output type '{body.name}' already exists")

    ot = OutputType(**body.model_dump())
    db.add(ot)
    await db.commit()
    await db.refresh(ot)
    # Reload with pricing_tier
    result2 = await db.execute(
        select(OutputType).options(selectinload(OutputType.pricing_tier)).where(OutputType.id == ot.id)
    )
    items = await _enrich_workflow_names(db, [_ot_to_out(result2.scalar_one())])
    return items[0]


@router.patch("/{output_type_id}", response_model=OutputTypeOut)
async def update_output_type(
    output_type_id: uuid.UUID,
    body: OutputTypePatch,
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(OutputType).where(OutputType.id == output_type_id))
    ot = result.scalar_one_or_none()
    if not ot:
        raise HTTPException(404, detail="Output type not found")

    data = body.model_dump(exclude_unset=True)
    if "render_backend" in data and data["render_backend"] not in VALID_RENDER_BACKENDS:
        raise HTTPException(400, detail=f"Invalid render_backend. Choose: {', '.join(sorted(VALID_RENDER_BACKENDS))}")

    for field_name, value in data.items():
        setattr(ot, field_name, value)
    await db.commit()
    await db.refresh(ot)
    # Reload with pricing_tier
    result2 = await db.execute(
        select(OutputType).options(selectinload(OutputType.pricing_tier)).where(OutputType.id == ot.id)
    )
    items = await _enrich_workflow_names(db, [_ot_to_out(result2.scalar_one())])
    return items[0]


@router.delete("/{output_type_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_output_type(
    output_type_id: uuid.UUID,
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(OutputType).where(OutputType.id == output_type_id))
    ot = result.scalar_one_or_none()
    if not ot:
        raise HTTPException(404, detail="Output type not found")

    # Check if referenced by order_lines
    usage = await db.execute(
        select(OrderLine).where(OrderLine.output_type_id == output_type_id).limit(1)
    )
    if usage.scalar_one_or_none():
        raise HTTPException(409, detail="Output type is referenced by existing order lines and cannot be deleted")

    await db.delete(ot)
    await db.commit()
