"""
KDS — Kitchen Display System router.

GET  /api/kds/orders
    Returns all open orders enriched with resolved modifier names,
    grouped by nothing (flat list). Frontend handles grouping/filtering.

PUT  /api/kds/orders/{order_id}/kds_status
    Update an order's KDS aggregate status (pending|preparing|done).
    Broadcasts kds_order_updated SSE event.

PUT  /api/kds/orders/{order_id}/items/{item_id}/kds_status
    Update a single item's KDS status (pending|preparing|done).
    Broadcasts kds_item_updated SSE event.

PUT  /api/kds/orders/{order_id}/order_type
    Update an order's type (here|takeaway|delivery).

--- Legacy endpoint kept for backward compat (printer-zone grouped items) ---
GET  /api/kds/items   (unchanged)
PUT  /api/kds/orders/{order_id}/items/{item_id}/status   (unchanged)
"""
import json
from fastapi import APIRouter, Depends, HTTPException, Query
from datetime import datetime, timezone
from sqlalchemy.orm import Session, joinedload
from pydantic import BaseModel
from typing import List, Optional

from database import get_db
from models.order import Order, OrderItem, OrderWaiter
from models.product import Product, ProductPreferenceSet
from models.prep_zone import PrepZone
from models.printer import Printer
from models.table import Table
from models.user import User
from models.message import StaffMessage, StaffMessageAck
from models.shift import WaiterShift
from routers.deps import get_current_user, require_kds
from services.sse_bus import broadcast_sync

router = APIRouter(dependencies=[Depends(require_kds)])

VALID_KDS_STATUSES = {"pending", "preparing", "done", "served", "declined"}
VALID_ORDER_TYPES  = {"here", "takeaway", "delivery"}


# ─────────────────────────── helpers ───────────────────────────────────────

def _resolve_item_modifiers(item: OrderItem, product: Product | None, db: Session):
    """
    Returns a dict with four lists:
      removed   - ingredient names removed (from removed_ingredients JSON id array)
      extras    - option names selected (from selected_options JSON id array)
      prefs     - preference choice names selected (from selected_options JSON id array)
      notes     - plain text note
    """
    removed, extras, prefs = [], [], []

    if product is None:
        return {"removed": removed, "extras": extras, "prefs": prefs, "notes": item.notes}

    # Build lookup maps (avoid N+1 — product relationships already loaded)
    ing_map   = {i.id: i.name for i in product.ingredients}
    opt_map   = {o.id: o.name for o in product.options}
    quick_map = {q.id: q.name for q in product.quick_options}

    # Preference choice lookup: pref_choice_map[choice_id] = (set_name, choice_name)
    pref_choice_map: dict[int, tuple[str, str]] = {}
    for ps in product.preference_sets:
        for pc in ps.choices:
            pref_choice_map[pc.id] = (ps.name, pc.name)

    # Removed ingredients — stored as JSON array of name strings (not ids)
    if item.removed_ingredients:
        try:
            vals = json.loads(item.removed_ingredients)
            for v in vals:
                if isinstance(v, str) and v:
                    removed.append(v)
                elif isinstance(v, int):
                    # legacy: id reference
                    name = ing_map.get(v)
                    if name:
                        removed.append(name)
        except (json.JSONDecodeError, TypeError):
            pass

    # Selected options — stored as JSON array of objects:
    # [{"id": int, "name": str, "type": "extra"|"quick"|"pref"|"pref_sub", ...}]
    # We use the stored name directly; type tag tells us which bucket.
    if item.selected_options:
        try:
            opts = json.loads(item.selected_options)
            for o in opts:
                if isinstance(o, dict):
                    name = o.get("name") or ""
                    otype = o.get("type", "")
                    if not name:
                        continue
                    if otype in ("pref", "pref_sub"):
                        prefs.append(name)
                    else:
                        # "extra", "extra_sub", "quick", or unknown → extras
                        extras.append(name)
                elif isinstance(o, int):
                    # legacy: id-only reference — fall back to lookup maps
                    if o in opt_map:
                        extras.append(opt_map[o])
                    elif o in quick_map:
                        extras.append(quick_map[o])
                    elif o in pref_choice_map:
                        _, choice_name = pref_choice_map[o]
                        prefs.append(choice_name)
        except (json.JSONDecodeError, TypeError):
            pass

    return {"removed": removed, "extras": extras, "prefs": prefs, "notes": item.notes}


def _build_order_payload(order: Order, tables_map: dict, db: Session) -> dict:
    """Build the full order dict the KDS frontend expects."""
    items_out = []
    for item in order.items:
        if item.status == "cancelled":
            continue
        if item.kds_status == "served":
            continue
        product = item.product
        mods = _resolve_item_modifiers(item, product, db)
        unit_type = product.unit_type if product else "piece"
        prep_zone_ids = [z.id for z in product.prep_zones] if product and hasattr(product, 'prep_zones') else []
        items_out.append({
            "id": item.id,
            "product_id": item.product_id,
            "product_name": product.name if product else f"#{item.product_id}",
            "quantity": item.quantity,
            "unit_type": unit_type or "piece",
            "kds_status": item.kds_status,
            "decline_note": item.decline_note,
            "added_at": item.added_at.isoformat() if item.added_at else None,
            "_added_at_raw": item.added_at,  # stripped before response
            "prep_zone_ids": prep_zone_ids,
            "course_id": item.course_id,
            "removed": mods["removed"],
            "extras": mods["extras"],
            "prefs": mods["prefs"],
            "notes": mods["notes"],
        })

    # Waiter names from first assignment
    waiter_names = [ow.waiter.username for ow in order.waiters if ow.waiter] if order.waiters else []

    # Clock = oldest added_at among pending/preparing items (the active batch).
    # Falls back to order.opened_at only if no such item exists.
    def _fmt_ts(dt):
        if dt is None:
            return None
        return dt.isoformat() + "Z" if not dt.tzinfo else dt.isoformat()

    active_batch_times = [
        item["_added_at_raw"] for item in items_out
        if item.get("_added_at_raw") and item["kds_status"] in ("pending", "preparing")
    ]
    batch_ts = min(active_batch_times) if active_batch_times else None
    opened_at_out = _fmt_ts(batch_ts) if batch_ts else _fmt_ts(order.opened_at)

    for item in items_out:
        item.pop("_added_at_raw", None)

    return {
        "id": order.id,
        "kds_status": order.kds_status,
        "order_type": order.order_type,
        "table_name": tables_map.get(order.table_id) if order.table_id else None,
        "table_id": order.table_id,
        "opened_at": opened_at_out,
        "closed_at": _fmt_ts(order.closed_at) if order.closed_at else None,
        "notes": order.notes,
        "waiters": waiter_names,
        "items": items_out,
    }


def _sync_order_kds_status(order: Order):
    """Derive and set order.kds_status from all non-cancelled items. Call before db.commit()."""
    relevant = [i for i in order.items if i.status != "cancelled"]
    if not relevant:
        return
    statuses = {i.kds_status for i in relevant}
    if statuses <= {"served"}:
        new_status = "served"
    elif statuses <= {"done", "served"}:
        new_status = "done"
    elif statuses <= {"declined"}:
        new_status = "declined"
    elif "preparing" in statuses or "done" in statuses:
        new_status = "preparing"
    else:
        new_status = "pending"
    if new_status != order.kds_status:
        order.kds_status = new_status
        order.kds_status_changed_at = datetime.now(timezone.utc)


# ─────────────────────────── endpoints ─────────────────────────────────────

@router.get("/orders")
def kds_orders(
    zone_id: Optional[int] = Query(default=None, description="Filter orders to only those containing items in this prep zone"),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Return all open/partially_paid/paid orders that still have unserved items.
    If zone_id is provided, only orders containing at least one item belonging to that prep zone are returned,
    and items not in that zone are stripped from the payload.
    """
    open_orders = (
        db.query(Order)
        .filter(Order.status.in_(["open", "partially_paid", "paid"]))
        .options(
            joinedload(Order.items).joinedload(OrderItem.product).joinedload(Product.ingredients),
            joinedload(Order.items).joinedload(OrderItem.product).joinedload(Product.options),
            joinedload(Order.items).joinedload(OrderItem.product).joinedload(Product.quick_options),
            joinedload(Order.items).joinedload(OrderItem.product).joinedload(Product.preference_sets).joinedload(ProductPreferenceSet.choices),
            joinedload(Order.items).joinedload(OrderItem.product).joinedload(Product.prep_zones),
            joinedload(Order.waiters).joinedload(OrderWaiter.waiter),
        )
        .order_by(Order.opened_at.asc())
        .all()
    )

    tables_map = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}

    payloads = []
    for o in open_orders:
        p = _build_order_payload(o, tables_map, db)
        if not p["items"]:
            continue
        # Zone filtering: keep only items whose product belongs to the requested zone
        if zone_id is not None:
            p["items"] = [it for it in p["items"] if zone_id in it.get("prep_zone_ids", [])]
            if not p["items"]:
                continue
        payloads.append(p)
    return {"orders": payloads}


class KdsStatusBody(BaseModel):
    status: str


class OrderTypeBody(BaseModel):
    order_type: str


@router.put("/orders/{order_id}/kds_status")
def update_order_kds_status(
    order_id: int,
    body: KdsStatusBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    if body.status not in VALID_KDS_STATUSES:
        raise HTTPException(status_code=400, detail=f"Invalid status '{body.status}'")

    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    if body.status != order.kds_status:
        order.kds_status = body.status
        order.kds_status_changed_at = datetime.now(timezone.utc)

    # Cascade order-level status to non-cancelled, non-served items only
    if body.status in ("pending", "preparing", "done"):
        for item in order.items:
            if item.status != "cancelled" and item.kds_status != "served":
                item.kds_status = body.status
                # auto_ready_to_served: if cascading to 'done', immediately upgrade qualifying items
                if body.status == "done" and item.product:
                    if any(bool(z.auto_ready_to_served) for z in item.product.prep_zones):
                        item.kds_status = "served"

    db.commit()

    broadcast_sync("kds_order_updated", {
        "order_id": order_id,
        "kds_status": body.status,
    })

    return {"order_id": order_id, "kds_status": body.status}


@router.put("/orders/{order_id}/items/{item_id}/kds_status")
def update_item_kds_status(
    order_id: int,
    item_id: int,
    body: KdsStatusBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    if body.status not in VALID_KDS_STATUSES:
        raise HTTPException(status_code=400, detail=f"Invalid status '{body.status}'")

    item = db.query(OrderItem).filter(
        OrderItem.id == item_id,
        OrderItem.order_id == order_id,
    ).first()
    if not item:
        raise HTTPException(status_code=404, detail="Item not found")

    item.kds_status = body.status

    # auto_ready_to_served: if item reaches 'done' and any of its zones has the flag,
    # immediately upgrade it to 'served' — item was ready at the KDS, no explicit serve needed.
    if body.status == "done" and item.product:
        auto_serve = any(bool(z.auto_ready_to_served) for z in item.product.prep_zones)
        if auto_serve:
            item.kds_status = "served"

    if item.order:
        _sync_order_kds_status(item.order)

    db.commit()

    broadcast_sync("kds_item_updated", {
        "order_id": order_id,
        "item_id": item_id,
        "kds_status": item.kds_status,
    })

    return {"item_id": item_id, "kds_status": item.kds_status}


@router.put("/orders/{order_id}/order_type")
def update_order_type(
    order_id: int,
    body: OrderTypeBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    if body.order_type not in VALID_ORDER_TYPES:
        raise HTTPException(status_code=400, detail=f"Invalid order_type '{body.order_type}'")

    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    order.order_type = body.order_type
    db.commit()

    return {"order_id": order_id, "order_type": body.order_type}


class MarkServedBody(BaseModel):
    item_ids: list[int]


@router.post("/orders/{order_id}/items/mark-served")
def mark_items_served(
    order_id: int,
    body: MarkServedBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Mark a list of items as served (kds_status=served). Works on active and paid items."""
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    updated = []
    ids_set = set(body.item_ids)
    for item in order.items:
        if item.id in ids_set:
            item.kds_status = "served"
            updated.append(item.id)

    _sync_order_kds_status(order)
    db.commit()

    broadcast_sync("kds_item_updated", {
        "order_id": order_id,
        "item_ids": updated,
        "kds_status": "served",
    })

    return {"updated": updated}


@router.post("/orders/{order_id}/items/mark-all-served")
def mark_all_items_served(
    order_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Mark all non-pending items in an order as served."""
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    updated = []
    for item in order.items:
        if item.kds_status in ("preparing", "done"):
            item.kds_status = "served"
            updated.append(item.id)

    _sync_order_kds_status(order)
    db.commit()

    broadcast_sync("kds_item_updated", {
        "order_id": order_id,
        "item_ids": updated,
        "kds_status": "served",
    })

    return {"updated": updated}


# ─────────────────────────── KDS print endpoint ─────────────────────────────

class KdsPrintBody(BaseModel):
    printer_id: Optional[int] = None
    item_ids: Optional[List[int]] = None
    copies: int = 1
    zone_id: Optional[int] = None
    mode: str = "primary"  # none | primary | all


@router.post("/orders/{order_id}/print")
def kds_print_order(
    order_id: int,
    body: KdsPrintBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Print order to zone printers. mode=primary prints to first printer, mode=all prints to all."""
    from services import printer_service
    if body.mode == "none":
        return {"printed": 0}
    if body.printer_id is not None:
        # Direct printer specified
        result = printer_service.print_to_printer(order_id, body.item_ids, body.printer_id, body.copies, db)
        return result
    # Zone-based print
    if body.zone_id is None:
        return {"printed": 0, "message": "No zone configured"}
    from models.prep_zone import PrepZone
    zone = db.query(PrepZone).filter(PrepZone.id == body.zone_id).first()
    if not zone or not zone.printers:
        return {"printed": 0, "message": "Zone has no printers"}
    printers_to_use = zone.printers if body.mode == "all" else [zone.printers[0]]
    printed = 0
    for printer in printers_to_use:
        try:
            printer_service.print_to_printer(order_id, body.item_ids, printer.id, body.copies, db)
            printed += 1
        except Exception:
            pass
    return {"printed": printed}


# ─────────────────────────── KDS decline endpoints ─────────────────────────

class DeclineBody(BaseModel):
    decline_note: Optional[str] = None  # reason string (preset or free text)


@router.put("/orders/{order_id}/decline")
def decline_order(
    order_id: int,
    body: DeclineBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Mark all active items on an order as declined."""
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    for item in order.items:
        if item.status != "cancelled":
            item.kds_status = "declined"
            item.decline_note = body.decline_note

    order.kds_status = "declined"
    db.commit()

    broadcast_sync("kds_order_updated", {
        "order_id": order_id,
        "kds_status": "declined",
        "decline_note": body.decline_note,
    })
    return {"order_id": order_id, "kds_status": "declined"}


@router.put("/orders/{order_id}/items/{item_id}/decline")
def decline_item(
    order_id: int,
    item_id: int,
    body: DeclineBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Mark a single item as declined."""
    item = db.query(OrderItem).filter(
        OrderItem.id == item_id,
        OrderItem.order_id == order_id,
    ).first()
    if not item:
        raise HTTPException(status_code=404, detail="Item not found")

    item.kds_status = "declined"
    item.decline_note = body.decline_note

    if item.order:
        _sync_order_kds_status(item.order)

    db.commit()

    broadcast_sync("kds_item_updated", {
        "order_id": order_id,
        "item_id": item_id,
        "kds_status": "declined",
        "decline_note": body.decline_note,
    })
    return {"item_id": item_id, "kds_status": "declined"}


# ─────────────────────────── KDS notifications ──────────────────────────────

def _waiter_ids_for_order(order: Order) -> list[int]:
    """Return the list of waiter IDs assigned to an order."""
    return [ow.waiter_id for ow in order.waiters if ow.waiter_id]


def _save_and_broadcast_message(
    db: Session,
    sender_id: int,
    body: str,
    target_waiter_ids: list[int],
    message_type: str,
    kds_zone: str | None,
    table_ids: list[int] | None = None,
) -> dict:
    """Persist a StaffMessage and broadcast via SSE. Returns the SSE payload dict."""
    from models.message import StaffMessage
    from datetime import datetime, timezone

    msg = StaffMessage(
        sender_id=sender_id,
        body=body,
        target_waiter_ids=json.dumps(target_waiter_ids),
        table_ids=json.dumps(table_ids or []),
        message_type=message_type,
        kds_zone=kds_zone,
    )
    db.add(msg)
    db.commit()
    db.refresh(msg)

    payload = {
        "id": msg.id,
        "sender_id": msg.sender_id,
        "sender_name": kds_zone or "KDS",
        "body": msg.body,
        "table_ids": msg.table_ids,
        "message_type": message_type,
        "kds_zone": kds_zone,
        "created_at": msg.created_at.isoformat() if msg.created_at else None,
    }
    user_ids = target_waiter_ids if target_waiter_ids else None
    broadcast_sync("message_sent", payload, user_ids=user_ids)
    return payload


class KdsNotifyBody(BaseModel):
    kds_zone: Optional[str] = None


class KdsCallWaiterBody(BaseModel):
    kds_zone: Optional[str] = None
    waiter_ids: List[int]  # for call_general: list of on-shift waiter ids to notify


@router.post("/orders/{order_id}/notify-complete")
def kds_notify_order_complete(
    order_id: int,
    body: KdsNotifyBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Notify assigned waiters that their order is fully complete."""
    order = db.query(Order).options(
        joinedload(Order.waiters),
    ).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    waiter_ids = _waiter_ids_for_order(order)
    if not waiter_ids:
        return {"sent": False, "reason": "no_waiters"}

    tables_map = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}
    table_name = tables_map.get(order.table_id, str(order.table_id)) if order.table_id else "Takeaway"
    zone = body.kds_zone or "Κουζίνα"

    msg_body = (
        f"Η παραγγελία #{order.id} για το τραπέζι {table_name} "
        f"είναι έτοιμη προς παραλαβή - {zone}"
    )
    _save_and_broadcast_message(
        db, user.id, msg_body, waiter_ids,
        "kds_order_done", zone,
        table_ids=[order.table_id] if order.table_id else [],
    )
    return {"sent": True, "waiter_ids": waiter_ids}


class KdsNotifyItemsBody(BaseModel):
    kds_zone: Optional[str] = None
    ready_count: int
    total_count: int


@router.post("/orders/{order_id}/notify-items-ready")
def kds_notify_items_ready(
    order_id: int,
    body: KdsNotifyItemsBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Notify assigned waiters that some (but not all) items are ready."""
    order = db.query(Order).options(
        joinedload(Order.waiters),
    ).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    waiter_ids = _waiter_ids_for_order(order)
    if not waiter_ids:
        return {"sent": False, "reason": "no_waiters"}

    tables_map = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}
    table_name = tables_map.get(order.table_id, str(order.table_id)) if order.table_id else "Takeaway"
    zone = body.kds_zone or "Κουζίνα"

    msg_body = (
        f"{body.ready_count} αντικείμενα από την παραγγελία #{order.id} "
        f"για το τραπέζι {table_name} είναι έτοιμα - {zone}"
    )
    _save_and_broadcast_message(
        db, user.id, msg_body, waiter_ids,
        "kds_item_done", zone,
        table_ids=[order.table_id] if order.table_id else [],
    )
    return {"sent": True, "waiter_ids": waiter_ids}


@router.post("/orders/{order_id}/call-waiter")
def kds_call_waiter_order(
    order_id: int,
    body: KdsNotifyBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Manually call the waiters assigned to a specific order."""
    order = db.query(Order).options(
        joinedload(Order.waiters),
    ).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")

    waiter_ids = _waiter_ids_for_order(order)
    if not waiter_ids:
        return {"sent": False, "reason": "no_waiters"}

    tables_map = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}
    table_name = tables_map.get(order.table_id, str(order.table_id)) if order.table_id else "Takeaway"
    zone = body.kds_zone or "Κουζίνα"

    msg_body = (
        f"Παραγγελία #{order.id} - τραπέζι {table_name} "
        f"- {zone}"
    )
    _save_and_broadcast_message(
        db, user.id, msg_body, waiter_ids,
        "kds_call_order", zone,
        table_ids=[order.table_id] if order.table_id else [],
    )
    return {"sent": True, "waiter_ids": waiter_ids}


@router.post("/call-waiter-general")
def kds_call_waiter_general(
    body: KdsCallWaiterBody,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Call one or more on-shift waiters to the prep zone."""
    if not body.waiter_ids:
        raise HTTPException(status_code=400, detail="waiter_ids must not be empty")

    zone = body.kds_zone or "Κουζίνα"
    msg_body = f"Κλήση από {zone}"
    _save_and_broadcast_message(
        db, user.id, msg_body, body.waiter_ids,
        "kds_call_general", zone,
    )
    return {"sent": True, "waiter_ids": body.waiter_ids}


@router.get("/on-shift-waiters")
def kds_on_shift_waiters(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Return waiters currently on shift (ended_at IS NULL)."""
    on_shift_ids = {
        row.waiter_id
        for row in db.query(WaiterShift).filter(WaiterShift.ended_at == None).all()
    }
    waiters = db.query(User).filter(
        User.id.in_(on_shift_ids),
        User.perm_access_waiter_app == True,
        User.is_active == True,
    ).order_by(User.username).all()
    return [
        {"id": w.id, "username": w.username, "nickname": w.nickname, "avatar_url": w.avatar_url}
        for w in waiters
    ]


# ─────────────────────────── legacy endpoints ──────────────────────────────

class ItemStatusUpdate(BaseModel):
    status: str  # only "ready" is accepted


@router.get("/items")
def kds_items(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Legacy: return active order items grouped by printer zone."""
    open_orders = db.query(Order).filter(Order.status.in_(["open", "partially_paid"])).all()
    order_ids = [o.id for o in open_orders]
    if not order_ids:
        return {"zones": []}

    order_map = {o.id: o for o in open_orders}
    items = db.query(OrderItem).filter(
        OrderItem.order_id.in_(order_ids),
        OrderItem.status == "active",
    ).order_by(OrderItem.added_at.asc()).all()

    tables_map = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}
    printers_map = {p.id: p.name for p in db.query(Printer).all()}

    zones: dict = {}

    def _zone_key(zone_id):
        return 0 if zone_id is None else zone_id

    def _zone_name(zone_id):
        if zone_id is None:
            return "Χωρίς Ζώνη"
        return printers_map.get(zone_id, f"Ζώνη #{zone_id}")

    for item in items:
        product = item.product
        zone_id = product.printer_zone_id if product else None
        zkey = _zone_key(zone_id)
        if zkey not in zones:
            zones[zkey] = {"zone_id": zone_id, "zone_name": _zone_name(zone_id), "items": []}
        order = order_map.get(item.order_id)
        table_name = tables_map.get(order.table_id) if order and order.table_id else None
        zones[zkey]["items"].append({
            "id": item.id,
            "order_id": item.order_id,
            "table_name": table_name,
            "product_name": product.name if product else f"#{item.product_id}",
            "quantity": item.quantity,
            "notes": item.notes,
            "added_at": item.added_at,
            "status": item.status,
        })

    zone_list = sorted(zones.values(), key=lambda z: (z["zone_id"] is None, z["zone_id"] or 0))
    return {"zones": zone_list}


@router.put("/orders/{order_id}/items/{item_id}/status")
def update_item_status_legacy(
    order_id: int,
    item_id: int,
    body: ItemStatusUpdate,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Legacy: mark an item ready (active → ready)."""
    if body.status != "ready":
        raise HTTPException(status_code=400, detail="Only 'ready' is a valid status transition via this endpoint")

    item = db.query(OrderItem).filter(
        OrderItem.id == item_id,
        OrderItem.order_id == order_id,
    ).first()
    if not item:
        raise HTTPException(status_code=404, detail="Item not found")
    if item.status != "active":
        raise HTTPException(status_code=400, detail=f"Item is '{item.status}' — only active items can be marked ready")

    item.status = "ready"
    db.commit()

    broadcast_sync("item_status_changed", {
        "order_id": order_id,
        "item_id": item_id,
        "status": "ready",
    })

    return {"status": "ready", "item_id": item_id}
