import json
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Optional

from database import get_db
from models.order import Order, OrderItem, OrderWaiter, OrderAuditLog
from models.user import User, WaiterZone
from models.table import Table
from models.product import Product
from schemas.order import OrderCreate, OrderOut, OrderItemOut, AddItemsRequest, AddItemsResponse, PayItemsRequest, OfflinePaymentRequest, AssignWaiterRequest, OrderWaiterOut
from pydantic import BaseModel

class PrintOrderRequest(BaseModel):
    printer_id: int

class TransferOrderRequest(BaseModel):
    target_table_id: int

class MergeOrderRequest(BaseModel):
    target_order_id: int

class SplitItemRequest(BaseModel):
    quantity: int  # how many to split off into a new item row

class PrintSynopsisRequest(BaseModel):
    printer_id: int

class MoveItemsRequest(BaseModel):
    item_ids: List[int]
    target_order_id: int

from routers.deps import get_current_user, require_manager
from services.printer_service import route_and_print, route_and_print_sync, print_order_receipt, print_order_synopsis
from services.sse_bus import broadcast_sync

router = APIRouter()


def _can_access_order(order: Order, user: User, db: Session) -> bool:
    """Zone-based access: any waiter whose zone covers the order's table group may act on it."""
    if user.role in ("manager", "sysadmin"):
        return True
    zones = db.query(WaiterZone).filter(WaiterZone.waiter_id == user.id).all()
    if not zones:
        return False
    if any(z.group_id is None for z in zones):
        return True
    table = db.query(Table).filter(Table.id == order.table_id).first()
    if not table:
        return False
    allowed_group_ids = {z.group_id for z in zones}
    return table.group_id in allowed_group_ids


def _audit(db: Session, order_id: int, event_type: str, waiter_id: int = None,
           item_ids: list = None, amount: float = None, payment_method: str = None, note: str = None):
    db.add(OrderAuditLog(
        order_id=order_id,
        event_type=event_type,
        waiter_id=waiter_id,
        item_ids=json.dumps(item_ids) if item_ids is not None else None,
        amount=amount,
        payment_method=payment_method,
        note=note,
    ))


@router.get("/", response_model=List[OrderOut])
def list_orders(
    order_status: Optional[str] = None,
    waiter_id: Optional[int] = None,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    q = db.query(Order)
    if order_status:
        q = q.filter(Order.status == order_status)
    if waiter_id:
        q = q.join(OrderWaiter).filter(OrderWaiter.waiter_id == waiter_id)
    return q.all()


@router.get("/my", response_model=List[OrderOut])
def my_orders(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    direct = db.query(Order).join(OrderWaiter).filter(
        OrderWaiter.waiter_id == user.id,
        Order.status.in_(["open", "partially_paid"]),
    ).all()
    # Also orders where user is opener but not explicitly assigned
    also_opened = db.query(Order).filter(
        Order.opened_by == user.id,
        Order.status.in_(["open", "partially_paid"]),
    ).all()
    seen = {o.id for o in direct}
    return direct + [o for o in also_opened if o.id not in seen]


class ActiveOrderSlim(BaseModel):
    id: int
    table_id: int
    status: str
    waiter_ids: List[int]
    model_config = {"from_attributes": True}


@router.get("/active", response_model=List[ActiveOrderSlim])
def list_active_orders(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    """All currently open/partially-paid/paid orders (lightweight). Accessible to all staff."""
    orders = db.query(Order).filter(Order.status.in_(["open", "partially_paid", "paid"])).all()
    return [
        ActiveOrderSlim(
            id=o.id,
            table_id=o.table_id,
            status=o.status,
            waiter_ids=[w.waiter_id for w in o.waiters],
        )
        for o in orders
    ]


@router.get("/{order_id}", response_model=OrderOut)
def get_order(order_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")
    return order


@router.post("/", response_model=OrderOut, status_code=status.HTTP_201_CREATED)
def open_order(body: OrderCreate, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    from models.business_day import BusinessDay
    from models.shift import WaiterShift

    active_day = db.query(BusinessDay).filter(BusinessDay.status == "open").first()
    if not active_day:
        raise HTTPException(status_code=403, detail="Restaurant is not open — manager must open the business day first")

    if user.role == "waiter":
        active_shift = db.query(WaiterShift).filter(
            WaiterShift.waiter_id == user.id,
            WaiterShift.ended_at == None,
        ).first()
        if not active_shift:
            raise HTTPException(status_code=403, detail="You do not have an active shift")

    existing = db.query(Order).filter(
        Order.table_id == body.table_id,
        Order.status.in_(["open", "partially_paid", "paid"]),
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Table already has an open order")
    order = Order(table_id=body.table_id, opened_by=user.id, business_day_id=active_day.id)
    db.add(order)
    db.flush()
    db.add(OrderWaiter(order_id=order.id, waiter_id=user.id))
    _audit(db, order.id, "ORDER_OPENED", waiter_id=user.id)
    db.commit()
    db.refresh(order)
    broadcast_sync("order_updated", {"order_id": order.id, "table_id": order.table_id, "status": order.status, "action": "opened"})
    return order


@router.post("/{order_id}/items", response_model=AddItemsResponse)
def add_items(
    order_id: int,
    body: AddItemsRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")
    if order.status not in ("open", "partially_paid", "paid"):
        raise HTTPException(status_code=400, detail="Order is not open")

    # Adding items to a fully-paid order reopens it — partially_paid since prior items were paid
    if order.status == "paid":
        order.status = "partially_paid"

    new_item_ids = []
    for item_in in body.items:
        product = db.query(Product).filter(Product.id == item_in.product_id).first()
        if not product or not product.is_available:
            raise HTTPException(status_code=400, detail=f"Product {item_in.product_id} not available")
        extra_cost = sum(
            (o.price_delta or o.extra_cost or 0.0)
            for o in (item_in.selected_options or [])
        )
        item = OrderItem(
            order_id=order_id,
            product_id=item_in.product_id,
            added_by=user.id,
            quantity=item_in.quantity,
            unit_price=product.base_price + extra_cost,
            selected_options=json.dumps([o.model_dump() for o in item_in.selected_options]) if item_in.selected_options else None,
            removed_ingredients=json.dumps(item_in.removed_ingredients) if item_in.removed_ingredients else None,
            notes=item_in.notes,
        )
        db.add(item)
        db.flush()
        new_item_ids.append(item.id)

    _audit(db, order_id, "ITEMS_ADDED", waiter_id=user.id, item_ids=new_item_ids)
    db.commit()
    db.refresh(order)

    print_results = route_and_print_sync(order_id, new_item_ids, db)
    broadcast_sync("order_updated", {"order_id": order.id, "table_id": order.table_id, "status": order.status, "action": "items_added", "item_ids": new_item_ids})
    return {"order": order, "print_results": print_results}


@router.post("/{order_id}/retry-print", response_model=AddItemsResponse)
def retry_print(
    order_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")

    unprinted_ids = [item.id for item in order.items if not item.printed and item.status == "active"]
    if not unprinted_ids:
        return {"order": order, "print_results": []}

    print_results = route_and_print_sync(order_id, unprinted_ids, db)
    db.refresh(order)
    return {"order": order, "print_results": print_results}


@router.put("/{order_id}/items/{item_id}", response_model=OrderItemOut)
def edit_item(order_id: int, item_id: int, notes: Optional[str] = None, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    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 notes is not None:
        item.notes = notes
    db.commit()
    db.refresh(item)
    return item


@router.delete("/{order_id}/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def cancel_item(order_id: int, item_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    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.status = "cancelled"
    _audit(db, order_id, "ITEM_CANCELLED", waiter_id=user.id, item_ids=[item_id])
    db.commit()


@router.post("/{order_id}/pay")
def pay_items(order_id: int, body: PayItemsRequest, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")

    from models.shift import WaiterShift

    items = db.query(OrderItem).filter(
        OrderItem.id.in_(body.item_ids),
        OrderItem.order_id == order_id,
        OrderItem.status == "active",
    ).all()
    now = datetime.now(timezone.utc)
    active_shift = db.query(WaiterShift).filter(
        WaiterShift.waiter_id == user.id,
        WaiterShift.ended_at == None,
    ).first()
    total_paid = 0.0
    for item in items:
        item.status = "paid"
        item.paid_by = user.id
        item.paid_at = now
        item.payment_method = body.payment_method
        item.paid_in_shift_id = active_shift.id if active_shift else None
        total_paid += item.unit_price * item.quantity

    db.flush()  # write item status changes before counting, since autoflush=False
    active_remaining = db.query(OrderItem).filter(
        OrderItem.order_id == order_id, OrderItem.status == "active"
    ).count()
    order.status = "paid" if active_remaining == 0 else "partially_paid"

    paid_ids = [i.id for i in items]
    _audit(db, order_id, "PAYMENT", waiter_id=user.id, item_ids=paid_ids,
           amount=total_paid, payment_method=body.payment_method)
    db.commit()
    broadcast_sync("order_paid", {"order_id": order_id, "table_id": order.table_id, "status": order.status, "paid_item_ids": paid_ids, "amount": total_paid, "payment_method": body.payment_method})
    return {"status": order.status, "paid_item_ids": paid_ids}


@router.post("/{order_id}/close")
def close_order(order_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")
    if order.status not in ("paid", "open", "partially_paid"):
        raise HTTPException(status_code=400, detail="Cannot close order in current status")
    order.status = "closed"
    order.closed_at = datetime.now(timezone.utc)
    order.closed_by = user.id
    _audit(db, order_id, "ORDER_CLOSED", waiter_id=user.id)
    db.commit()
    broadcast_sync("order_closed", {"order_id": order_id, "table_id": order.table_id})
    return {"status": "closed"}


@router.post("/{order_id}/pay-offline")
def pay_items_offline(
    order_id: int,
    body: OfflinePaymentRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """
    Sync an emergency payment that was taken while the server was offline.
    The UUID prevents double-processing. If a payment with the same UUID already
    exists on this order, the duplicate is logged in red (is_duplicate=1) rather
    than silently dropped — so managers can reconcile.
    """
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")

    # Check for duplicate UUID on this order
    existing_uuid = db.query(OrderAuditLog).filter(
        OrderAuditLog.order_id == order_id,
        OrderAuditLog.offline_uuid == body.uuid,
    ).first()
    is_duplicate = existing_uuid is not None

    from models.shift import WaiterShift
    items = db.query(OrderItem).filter(
        OrderItem.id.in_(body.item_ids),
        OrderItem.order_id == order_id,
        OrderItem.status == "active",
    ).all()

    # Reject empty payments — client had no offline snapshot for this table
    if not items and not is_duplicate:
        raise HTTPException(status_code=400, detail="No active items found — payment rejected")

    # Use the client-recorded offline timestamp as paid_at so audit reflects real payment time
    try:
        paid_at = datetime.fromisoformat(body.offline_at.replace("Z", "+00:00")) if body.offline_at else datetime.now(timezone.utc)
    except (ValueError, AttributeError):
        paid_at = datetime.now(timezone.utc)

    active_shift = db.query(WaiterShift).filter(
        WaiterShift.waiter_id == user.id,
        WaiterShift.ended_at == None,
    ).first()

    total_paid = 0.0
    paid_ids = []
    if not is_duplicate:
        for item in items:
            item.status = "paid"
            item.paid_by = user.id
            item.paid_at = paid_at
            item.payment_method = body.payment_method
            item.paid_in_shift_id = active_shift.id if active_shift else None
            total_paid += item.unit_price * item.quantity
            paid_ids.append(item.id)

        db.flush()
        active_remaining = db.query(OrderItem).filter(
            OrderItem.order_id == order_id, OrderItem.status == "active"
        ).count()
        order.status = "paid" if active_remaining == 0 else "partially_paid"
    else:
        # Duplicate — compute total for audit record without changing item state
        total_paid = sum(i.unit_price * i.quantity for i in items)
        paid_ids = [i.id for i in items]

    # Always write audit log — duplicate flag makes it visible in red in manager dashboard
    db.add(OrderAuditLog(
        order_id=order_id,
        event_type="PAYMENT_OFFLINE",
        waiter_id=user.id,
        item_ids=json.dumps(paid_ids),
        amount=total_paid,
        payment_method=body.payment_method,
        note=f"Emergency offline payment (uuid={body.uuid}){' — DUPLICATE' if is_duplicate else ''}",
        offline_uuid=body.uuid,
        offline_at=body.offline_at,
        is_duplicate=1 if is_duplicate else 0,
    ))
    db.commit()

    if not is_duplicate:
        broadcast_sync("order_paid", {"order_id": order_id, "table_id": order.table_id, "status": order.status, "paid_item_ids": paid_ids, "amount": total_paid, "payment_method": body.payment_method})

    return {
        "status": order.status if not is_duplicate else "duplicate",
        "paid_item_ids": paid_ids,
        "is_duplicate": is_duplicate,
    }


@router.delete("/{order_id}", status_code=status.HTTP_204_NO_CONTENT)
def cancel_order(order_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    order.status = "cancelled"
    order.closed_at = datetime.now(timezone.utc)
    order.closed_by = user.id
    _audit(db, order_id, "ORDER_CANCELLED", waiter_id=user.id)
    db.commit()
    broadcast_sync("order_closed", {"order_id": order_id, "table_id": order.table_id})


@router.put("/{order_id}/assign-waiter")
def assign_waiter(order_id: int, body: AssignWaiterRequest, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    existing = db.query(OrderWaiter).filter(
        OrderWaiter.order_id == order_id, OrderWaiter.waiter_id == body.waiter_id
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Waiter already assigned")
    db.add(OrderWaiter(order_id=order_id, waiter_id=body.waiter_id))
    db.commit()
    return {"status": "assigned"}


@router.delete("/{order_id}/waiters/{waiter_id}", status_code=status.HTTP_204_NO_CONTENT)
def remove_waiter(order_id: int, waiter_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    assignment = db.query(OrderWaiter).filter(
        OrderWaiter.order_id == order_id, OrderWaiter.waiter_id == waiter_id
    ).first()
    if not assignment:
        raise HTTPException(status_code=404, detail="Assignment not found")
    db.delete(assignment)
    db.commit()


@router.post("/{order_id}/print")
def print_order(
    order_id: int,
    body: PrintOrderRequest,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    from models.printer import Printer

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

    printer = db.query(Printer).filter(Printer.id == body.printer_id, Printer.is_active == True).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found or inactive")

    table = db.query(Table).filter(Table.id == order.table_id).first()
    table_name = (table.label or f"T{table.number}") if table else f"#{order.table_id}"

    opener = db.query(User).filter(User.id == order.opened_by).first()
    waiter_name = opener.username if opener else f"#{order.opened_by}"

    items_data = []
    for item in order.items:
        if item.status == "cancelled":
            continue
        product_name = item.product.name if item.product else f"#{item.product_id}"
        items_data.append({
            "name": product_name,
            "quantity": item.quantity,
            "unit_price": item.unit_price,
            "total": item.unit_price * item.quantity,
            "status": item.status,
        })

    grand_total = sum(i["total"] for i in items_data)

    receipt = {
        "order_id": order.id,
        "table_name": table_name,
        "waiter_name": waiter_name,
        "opened_at": order.opened_at.strftime("%d/%m/%Y %H:%M"),
        "closed_at": order.closed_at.strftime("%d/%m/%Y %H:%M") if order.closed_at else None,
        "status": order.status,
        "items": items_data,
        "total": grand_total,
        "notes": order.notes,
    }

    background_tasks.add_task(print_order_receipt, printer.ip_address, printer.port, receipt)
    return {"status": "printing"}


# ─── Transfer order to a different table ─────────────────────────────────────

@router.post("/{order_id}/transfer")
def transfer_order(
    order_id: int,
    body: TransferOrderRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")
    if order.status not in ("open", "partially_paid", "paid"):
        raise HTTPException(status_code=400, detail="Order is not active")

    target_table = db.query(Table).filter(Table.id == body.target_table_id, Table.is_active == True).first()
    if not target_table:
        raise HTTPException(status_code=404, detail="Target table not found")
    if body.target_table_id == order.table_id:
        raise HTTPException(status_code=400, detail="Table is already assigned to this order")

    conflict = db.query(Order).filter(
        Order.table_id == body.target_table_id,
        Order.status.in_(["open", "partially_paid", "paid"]),
    ).first()
    if conflict:
        raise HTTPException(status_code=400, detail="Target table already has an active order")

    old_table_id = order.table_id
    order.table_id = body.target_table_id
    _audit(db, order_id, "TABLE_TRANSFER", waiter_id=user.id,
           note=f"Transferred from table {old_table_id} to table {body.target_table_id}")
    db.commit()
    db.refresh(order)
    broadcast_sync("order_updated", {"order_id": order.id, "table_id": order.table_id, "old_table_id": old_table_id, "status": order.status, "action": "transferred"})
    return order


# ─── Merge another order into this one ───────────────────────────────────────

@router.post("/{order_id}/merge")
def merge_order(
    order_id: int,
    body: MergeOrderRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """
    Merge source order (order_id) INTO target order (body.target_order_id).
    All items (paid + active) from the source are reassigned to the target.
    Source waiters are added to the target if not already there.
    Source order is cancelled with audit note.
    """
    source = db.query(Order).filter(Order.id == order_id).first()
    if not source:
        raise HTTPException(status_code=404, detail="Source order not found")
    if not _can_access_order(source, user, db):
        raise HTTPException(status_code=403, detail="Access denied")
    if source.status not in ("open", "partially_paid", "paid"):
        raise HTTPException(status_code=400, detail="Source order is not active")

    target = db.query(Order).filter(Order.id == body.target_order_id).first()
    if not target:
        raise HTTPException(status_code=404, detail="Target order not found")
    if not _can_access_order(target, user, db):
        raise HTTPException(status_code=403, detail="Access denied to target order")
    if target.status not in ("open", "partially_paid", "paid"):
        raise HTTPException(status_code=400, detail="Target order is not active")
    if source.id == target.id:
        raise HTTPException(status_code=400, detail="Cannot merge an order with itself")

    # Move all items to target order
    moved_item_ids = []
    for item in source.items:
        item.order_id = target.id
        moved_item_ids.append(item.id)

    # Copy source waiters to target (no duplicates)
    existing_waiter_ids = {w.waiter_id for w in target.waiters}
    for ow in source.waiters:
        if ow.waiter_id not in existing_waiter_ids:
            db.add(OrderWaiter(order_id=target.id, waiter_id=ow.waiter_id))

    # Recompute target status after flush
    db.flush()
    active_remaining = db.query(OrderItem).filter(
        OrderItem.order_id == target.id, OrderItem.status == "active"
    ).count()
    paid_exists = db.query(OrderItem).filter(
        OrderItem.order_id == target.id, OrderItem.status == "paid"
    ).count()
    if active_remaining > 0:
        target.status = "partially_paid" if paid_exists > 0 else "open"
    else:
        target.status = "paid"

    # Cancel source order
    source.status = "cancelled"
    source.closed_at = datetime.now(timezone.utc)
    source.closed_by = user.id

    _audit(db, source.id, "ORDER_CANCELLED", waiter_id=user.id,
           note=f"Merged into order #{target.id} (table {target.table_id})")
    _audit(db, target.id, "ITEMS_ADDED", waiter_id=user.id, item_ids=moved_item_ids,
           note=f"Items merged from order #{source.id} (table {source.table_id})")

    db.commit()
    db.refresh(target)
    broadcast_sync("order_updated", {"order_id": target.id, "table_id": target.table_id, "status": target.status, "action": "merged"})
    broadcast_sync("order_closed", {"order_id": source.id, "table_id": source.table_id})
    return target


# ─── Split a stacked item into two rows ──────────────────────────────────────

@router.post("/{order_id}/items/{item_id}/split", response_model=List[OrderItemOut])
def split_item(
    order_id: int,
    item_id: int,
    body: SplitItemRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """
    Split qty units off item_id into a new item row.
    Both rows share all properties (product, price, options, notes).
    Only active items can be split.
    """
    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="Only active items can be split")
    if body.quantity <= 0 or body.quantity >= item.quantity:
        raise HTTPException(
            status_code=400,
            detail=f"Split quantity must be between 1 and {item.quantity - 1}"
        )

    order = db.query(Order).filter(Order.id == order_id).first()
    if not _can_access_order(order, user, db):
        raise HTTPException(status_code=403, detail="Access denied")

    # Reduce original item
    item.quantity -= body.quantity

    # Create split-off item
    new_item = OrderItem(
        order_id=order_id,
        product_id=item.product_id,
        added_by=item.added_by,
        quantity=body.quantity,
        unit_price=item.unit_price,
        selected_options=item.selected_options,
        removed_ingredients=item.removed_ingredients,
        notes=item.notes,
        status="active",
        printed=item.printed,
    )
    db.add(new_item)
    db.commit()
    db.refresh(item)
    db.refresh(new_item)
    return [item, new_item]


# ─── Move selected items to another order ────────────────────────────────────

@router.post("/{order_id}/move-items")
def move_items(
    order_id: int,
    body: MoveItemsRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Move specific active items from this order to another open order."""
    source = db.query(Order).filter(Order.id == order_id).first()
    if not source:
        raise HTTPException(status_code=404, detail="Source order not found")
    if not _can_access_order(source, user, db):
        raise HTTPException(status_code=403, detail="Access denied")
    if source.status not in ("open", "partially_paid"):
        raise HTTPException(status_code=400, detail="Source order is not active")

    target = db.query(Order).filter(Order.id == body.target_order_id).first()
    if not target:
        raise HTTPException(status_code=404, detail="Target order not found")
    if not _can_access_order(target, user, db):
        raise HTTPException(status_code=403, detail="Access denied to target order")
    if target.status not in ("open", "partially_paid"):
        raise HTTPException(status_code=400, detail="Target order is not active")
    if source.id == target.id:
        raise HTTPException(status_code=400, detail="Source and target orders are the same")

    items = db.query(OrderItem).filter(
        OrderItem.id.in_(body.item_ids),
        OrderItem.order_id == order_id,
        OrderItem.status == "active",
    ).all()
    if not items:
        raise HTTPException(status_code=400, detail="No active items found to move")

    moved_ids = []
    for item in items:
        item.order_id = target.id
        moved_ids.append(item.id)

    # Recompute source status
    db.flush()
    src_active = db.query(OrderItem).filter(OrderItem.order_id == source.id, OrderItem.status == "active").count()
    src_paid = db.query(OrderItem).filter(OrderItem.order_id == source.id, OrderItem.status == "paid").count()
    if src_active == 0 and src_paid == 0:
        source.status = "open"
    elif src_active == 0:
        source.status = "paid"
    else:
        source.status = "partially_paid" if src_paid > 0 else "open"

    # Recompute target status
    tgt_active = db.query(OrderItem).filter(OrderItem.order_id == target.id, OrderItem.status == "active").count()
    tgt_paid = db.query(OrderItem).filter(OrderItem.order_id == target.id, OrderItem.status == "paid").count()
    target.status = "partially_paid" if (tgt_active > 0 and tgt_paid > 0) else ("paid" if tgt_active == 0 else "open")

    _audit(db, source.id, "ITEMS_MOVED_OUT", waiter_id=user.id, item_ids=moved_ids,
           note=f"Moved to order #{target.id} (table {target.table_id})")
    _audit(db, target.id, "ITEMS_MOVED_IN", waiter_id=user.id, item_ids=moved_ids,
           note=f"Moved from order #{source.id} (table {source.table_id})")

    db.commit()
    db.refresh(source)
    return {"moved_item_ids": moved_ids, "source_status": source.status, "target_status": target.status}


# ─── Print order synopsis ─────────────────────────────────────────────────────

@router.post("/{order_id}/print-synopsis")
def print_synopsis(
    order_id: int,
    body: PrintSynopsisRequest,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    from models.printer import Printer

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

    printer = db.query(Printer).filter(Printer.id == body.printer_id, Printer.is_active == True).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found or inactive")

    table = db.query(Table).filter(Table.id == order.table_id).first()
    table_name = (table.label or f"T{table.number}") if table else f"#{order.table_id}"
    opener = db.query(User).filter(User.id == order.opened_by).first()
    waiter_name = (opener.nickname or opener.username) if opener else f"#{order.opened_by}"

    items_data = []
    for item in order.items:
        if item.status == "cancelled":
            continue
        product_name = item.product.name if item.product else f"#{item.product_id}"
        items_data.append({
            "name": product_name,
            "quantity": item.quantity,
            "unit_price": item.unit_price,
            "total": item.unit_price * item.quantity,
            "status": item.status,
        })

    total = sum(i["total"] for i in items_data)
    paid_total = sum(i["total"] for i in items_data if i["status"] == "paid")

    synopsis = {
        "order_id": order.id,
        "table_name": table_name,
        "waiter_name": waiter_name,
        "opened_at": order.opened_at.strftime("%d/%m/%Y %H:%M"),
        "items": items_data,
        "total": total,
        "paid_total": paid_total,
        "remaining": total - paid_total,
    }

    background_tasks.add_task(print_order_synopsis, printer.ip_address, printer.port, synopsis)
    return {"status": "printing"}
