"""Order items router - manage individual line items within an order."""
import uuid
from datetime import datetime
from typing import Optional

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

from app.database import get_db
from app.models.cad_file import CadFile
from app.models.order import Order, OrderStatus
from app.models.order_item import OrderItem, ItemStatus
from app.models.user import User
from app.schemas.order import OrderItemOut
from app.utils.auth import get_current_user

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


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _is_privileged(user: User) -> bool:
    return user.role.value in ("admin", "project_manager")


async def _get_order_and_item(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    user: User,
    db: AsyncSession,
) -> tuple[Order, OrderItem]:
    """Load order + item, enforcing ownership/admin access."""
    order_result = await db.execute(select(Order).where(Order.id == order_id))
    order = order_result.scalar_one_or_none()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _is_privileged(user) and order.created_by != user.id:
        raise HTTPException(status_code=403, detail="Access denied")

    item_result = await db.execute(
        select(OrderItem)
        .options(selectinload(OrderItem.cad_file))
        .where(
            OrderItem.id == item_id,
            OrderItem.order_id == order_id,
        )
    )
    item = item_result.scalar_one_or_none()
    if not item:
        raise HTTPException(status_code=404, detail="Order item not found")

    return order, item


# ---------------------------------------------------------------------------
# Request schemas
# ---------------------------------------------------------------------------

class OrderItemPatch(BaseModel):
    ebene1: Optional[str] = None
    ebene2: Optional[str] = None
    baureihe: Optional[str] = None
    pim_id: Optional[str] = None
    produkt_baureihe: Optional[str] = None
    gewaehltes_produkt: Optional[str] = None
    name_cad_modell: Optional[str] = None
    gewuenschte_bildnummer: Optional[str] = None
    lagertyp: Optional[str] = None
    medias_rendering: Optional[bool] = None
    notes: Optional[str] = None


class ApproveRejectBody(BaseModel):
    notes: Optional[str] = None


class CadPartMaterialEntry(BaseModel):
    part_name: str
    material: str


class CadPartMaterialsBody(BaseModel):
    parts: list[CadPartMaterialEntry]


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------

@router.get("/{order_id}/items", response_model=list[OrderItemOut])
async def list_order_items(
    order_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Return all items belonging to an order."""
    order_result = await db.execute(select(Order).where(Order.id == order_id))
    order = order_result.scalar_one_or_none()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _is_privileged(user) and order.created_by != user.id:
        raise HTTPException(status_code=403, detail="Access denied")

    items_result = await db.execute(
        select(OrderItem)
        .options(selectinload(OrderItem.cad_file))
        .where(OrderItem.order_id == order_id)
        .order_by(OrderItem.row_index)
    )
    items = items_result.scalars().all()
    return [OrderItemOut.model_validate(i) for i in items]


@router.get("/{order_id}/items/{item_id}", response_model=OrderItemOut)
async def get_order_item(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Return a single order item."""
    _, item = await _get_order_and_item(order_id, item_id, user, db)
    return OrderItemOut.model_validate(item)


@router.patch("/{order_id}/items/{item_id}", response_model=OrderItemOut)
async def update_order_item(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    body: OrderItemPatch,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Edit the standard (non-component) fields of an order item."""
    order, item = await _get_order_and_item(order_id, item_id, user, db)

    # Only draft orders can be edited (admins may also edit submitted orders)
    if not _is_privileged(user) and order.status != OrderStatus.draft:
        raise HTTPException(
            status_code=400,
            detail="Order items can only be edited while the order is in draft status",
        )

    patch_data = body.model_dump(exclude_unset=True)
    for field, value in patch_data.items():
        setattr(item, field, value)

    item.updated_at = datetime.utcnow()
    await db.commit()
    refreshed = await db.execute(
        select(OrderItem).options(selectinload(OrderItem.cad_file)).where(OrderItem.id == item_id)
    )
    return OrderItemOut.model_validate(refreshed.scalar_one())


@router.post(
    "/{order_id}/items/{item_id}/approve",
    response_model=OrderItemOut,
    status_code=status.HTTP_200_OK,
)
async def approve_order_item(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    body: ApproveRejectBody = ApproveRejectBody(),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Mark an order item as approved (admin only)."""
    if not _is_privileged(user):
        raise HTTPException(status_code=403, detail="Only admins or PMs can approve items")

    _, item = await _get_order_and_item(order_id, item_id, user, db)

    if item.item_status == ItemStatus.approved:
        raise HTTPException(status_code=400, detail="Item is already approved")

    item.item_status = ItemStatus.approved
    if body.notes is not None:
        item.notes = body.notes
    item.updated_at = datetime.utcnow()

    await db.commit()
    refreshed = await db.execute(
        select(OrderItem).options(selectinload(OrderItem.cad_file)).where(OrderItem.id == item_id)
    )
    return OrderItemOut.model_validate(refreshed.scalar_one())


@router.post(
    "/{order_id}/items/{item_id}/reject",
    response_model=OrderItemOut,
    status_code=status.HTTP_200_OK,
)
async def reject_order_item(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    body: ApproveRejectBody = ApproveRejectBody(),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Mark an order item as rejected (admin only)."""
    if not _is_privileged(user):
        raise HTTPException(status_code=403, detail="Only admins or PMs can reject items")

    _, item = await _get_order_and_item(order_id, item_id, user, db)

    if item.item_status == ItemStatus.rejected:
        raise HTTPException(status_code=400, detail="Item is already rejected")

    item.item_status = ItemStatus.rejected
    if body.notes is not None:
        item.notes = body.notes
    item.updated_at = datetime.utcnow()

    await db.commit()
    refreshed = await db.execute(
        select(OrderItem).options(selectinload(OrderItem.cad_file)).where(OrderItem.id == item_id)
    )
    return OrderItemOut.model_validate(refreshed.scalar_one())


@router.put(
    "/{order_id}/items/{item_id}/cad-materials",
    response_model=OrderItemOut,
)
async def update_cad_materials(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    body: CadPartMaterialsBody,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Save material assignments for each CAD part of an order item."""
    _, item = await _get_order_and_item(order_id, item_id, user, db)
    from sqlalchemy import update as sql_update
    await db.execute(
        sql_update(OrderItem)
        .where(OrderItem.id == item_id)
        .values(
            cad_part_materials=[e.model_dump() for e in body.parts],
            updated_at=datetime.utcnow(),
        )
    )
    await db.commit()
    # Re-fetch with cad_file eagerly loaded so cad_parsed_objects property works
    refreshed = await db.execute(
        select(OrderItem)
        .options(selectinload(OrderItem.cad_file))
        .where(OrderItem.id == item_id)
    )
    updated_item = refreshed.scalar_one()

    # Queue thumbnail re-render with part colours if the item has a linked CAD file
    if updated_item.cad_file_id and updated_item.cad_file:
        parsed_objects = (updated_item.cad_file.parsed_objects or {}).get("objects", [])
        if parsed_objects:
            from app.services.step_processor import build_part_colors
            from app.tasks.step_tasks import regenerate_thumbnail
            part_colors = build_part_colors(
                parsed_objects,
                [e.model_dump() for e in body.parts],
            )
            regenerate_thumbnail.delay(str(updated_item.cad_file_id), part_colors)

    return OrderItemOut.model_validate(updated_item)


@router.delete(
    "/{order_id}/items/{item_id}/cad-file",
    status_code=status.HTTP_204_NO_CONTENT,
)
async def unlink_cad_file(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """
    Unlink the STEP/CAD file from an order item.

    Clears cad_file_id and cad_part_materials on the item.
    If no other items reference the same CadFile, deletes the record and
    removes the stored STEP, thumbnail, and glTF files from disk.
    Only allowed while the order is in draft status.
    """
    import os
    from sqlalchemy import update as sql_update, func

    order, item = await _get_order_and_item(order_id, item_id, user, db)

    if order.status != OrderStatus.draft:
        raise HTTPException(400, detail="CAD file can only be removed from draft orders")

    if not item.cad_file_id:
        raise HTTPException(400, detail="This item has no CAD file linked")

    cad_id = item.cad_file_id

    # Fetch the CadFile before unlinking
    cad_result = await db.execute(select(CadFile).where(CadFile.id == cad_id))
    cad_file = cad_result.scalar_one_or_none()

    # Unlink item
    from sqlalchemy import update as sql_update
    await db.execute(
        sql_update(OrderItem)
        .where(OrderItem.id == item_id)
        .values(cad_file_id=None, cad_part_materials=[], updated_at=datetime.utcnow())
    )
    await db.commit()

    # Delete CadFile record + disk files if no other items still reference it
    if cad_file:
        remaining = await db.execute(
            select(func.count()).where(OrderItem.cad_file_id == cad_id)
        )
        if remaining.scalar() == 0:
            for path_attr in ("stored_path", "thumbnail_path", "gltf_path"):
                fpath = getattr(cad_file, path_attr, None)
                if fpath:
                    try:
                        os.remove(fpath)
                    except FileNotFoundError:
                        pass
            await db.delete(cad_file)
            await db.commit()


@router.post(
    "/{order_id}/items/{item_id}/regenerate-thumbnail",
    status_code=status.HTTP_202_ACCEPTED,
)
async def regenerate_item_thumbnail(
    order_id: uuid.UUID,
    item_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """
    Queue a thumbnail re-render for an order item's linked CAD file.

    The thumbnail is re-generated with per-part colours derived from the
    currently saved cad_part_materials.  Returns immediately; the worker
    processes the job asynchronously.
    """
    _, item = await _get_order_and_item(order_id, item_id, user, db)

    if not item.cad_file_id:
        raise HTTPException(400, detail="No CAD file linked to this item")
    if not item.cad_file:
        raise HTTPException(400, detail="CAD file record not found")

    parsed_objects = (item.cad_file.parsed_objects or {}).get("objects", [])
    from app.services.step_processor import build_part_colors
    from app.tasks.step_tasks import regenerate_thumbnail

    part_colors = build_part_colors(parsed_objects, item.cad_part_materials or [])
    task = regenerate_thumbnail.delay(str(item.cad_file_id), part_colors)

    return {"status": "queued", "task_id": task.id, "cad_file_id": str(item.cad_file_id)}
