import { apiPost } from "@/lib/api/client";
import type { Order, OrderItemLine } from "@/lib/api/types";
import type { CartItem } from "@/lib/stores/cart.store";
import { iranMobileForApi } from "@/lib/phone";
import { enqueueOutboxOp, getOutboxCount, getQueueCount } from "@/lib/offline/offline-db";
import { isNetworkError, newIdempotencyKey, newLocalId } from "@/lib/offline/offline-write";
import { useSyncQueueStore } from "@/lib/stores/sync-queue.store";

export type SubmitOrderCart = {
  getPendingLines: () => { menuItemId: string; quantity: number; notes?: string }[];
  activeOrderId: string | null;
  tableId: string | null;
  guestName: string;
  guestPhone: string;
  customerId: string | null;
  appliedCoupon: { id: string } | null;
};

export type SubmitOrderParams = {
  cafeId: string;
  orderBranchId: string | undefined;
  cart: SubmitOrderCart;
  reservationId: string | null;
  /** Cart items (needed to build the offline mock order) */
  cartItems?: CartItem[];
};

// ─── Helpers ────────────────────────────────────────────────────────────────
// isNetworkError / newLocalId / newIdempotencyKey are shared from offline-write.

/** Body for a create-order POST. */
function buildCreateBody(
  params: SubmitOrderParams,
  pending: ReturnType<SubmitOrderCart["getPendingLines"]>
) {
  const { cart, orderBranchId, reservationId } = params;
  return {
    orderType: "DineIn",
    branchId: orderBranchId,
    tableId: cart.tableId ?? undefined,
    reservationId: reservationId ?? undefined,
    guestName: cart.guestName.trim() || undefined,
    guestPhone: iranMobileForApi(cart.guestPhone),
    customerId: cart.customerId ?? undefined,
    couponId: cart.appliedCoupon?.id,
    items: pending,
  };
}

/** Build a synthetic Order so the POS stays usable offline. Uses the supplied
 * id so it matches the outbox op's createsClientId (enabling later remap). */
function buildLocalOrder(params: SubmitOrderParams, cartItems: CartItem[], orderId: string): Order {
  const pending = params.cart.getPendingLines();

  const items: OrderItemLine[] = pending.map((p) => {
    const ci = cartItems.find((c) => c.menuItem.id === p.menuItemId);
    return {
      id: newLocalId(),
      menuItemId: p.menuItemId,
      menuItemName: ci?.menuItem.name ?? p.menuItemId,
      quantity: p.quantity,
      unitPrice: ci?.menuItem.price ?? 0,
      notes: p.notes,
      isVoided: false,
    };
  });

  const subtotal = items.reduce((s, i) => s + i.unitPrice * i.quantity, 0);
  const taxTotal = Math.round(subtotal * 0.09);
  const total = subtotal + taxTotal;

  return {
    id: orderId,
    cafeId: params.cafeId,
    branchId: params.orderBranchId,
    tableId: params.cart.tableId ?? undefined,
    guestName: params.cart.guestName.trim() || undefined,
    guestPhone: iranMobileForApi(params.cart.guestPhone) ?? undefined,
    customerId: params.cart.customerId ?? undefined,
    orderType: "DineIn",
    status: "Open",
    subtotal,
    taxTotal,
    discountAmount: 0,
    total,
    paidAmount: 0,
    createdAt: new Date().toISOString(),
    displayNumber: 0,
    items,
    payments: [],
  };
}

async function refreshQueueBadge(): Promise<void> {
  const count = (await getOutboxCount()) + (await getQueueCount());
  useSyncQueueStore.getState().setQueueCount(count);
}

/**
 * Queue the write and return a local mock order. Two cases:
 *  - create: enqueue POST /orders with a fresh local id as createsClientId;
 *  - add items: enqueue POST /orders/{id}/items. {id} may be a local id — the
 *    outbox blocks then remaps it once the create syncs.
 */
async function queueAndBuildLocalOrder(
  params: SubmitOrderParams,
  cartItems: CartItem[],
  idempotencyKey: string
): Promise<Order> {
  const { cafeId, cart } = params;
  const pending = cart.getPendingLines();
  if (pending.length === 0) throw new Error("nothing pending");

  const activeId = cart.activeOrderId;

  if (activeId) {
    // Add items to an existing order (real server id, or a not-yet-synced local id).
    await enqueueOutboxOp({
      id: newLocalId(),
      idempotencyKey,
      method: "POST",
      url: `/api/cafes/${cafeId}/orders/${activeId}/items`,
      body: { items: pending },
      entityType: "order_items",
      createdAt: Date.now(),
    });
    await refreshQueueBadge();
    return buildLocalOrder(params, cartItems, activeId);
  }

  // Create a brand-new order. createsClientId lets later add-items ops remap.
  const localOrderId = newLocalId();
  await enqueueOutboxOp({
    id: newLocalId(),
    idempotencyKey,
    method: "POST",
    url: `/api/cafes/${cafeId}/orders`,
    body: buildCreateBody(params, pending),
    entityType: "order",
    createsClientId: localOrderId,
    idField: "id",
    createdAt: Date.now(),
  });
  await refreshQueueBadge();
  return buildLocalOrder(params, cartItems, localOrderId);
}

// ─── Main export ──────────────────────────────────────────────────────────────

export async function submitOrderToApi({
  cafeId,
  orderBranchId,
  cart,
  reservationId,
  cartItems = [],
}: SubmitOrderParams): Promise<Order> {
  const params: SubmitOrderParams = { cafeId, orderBranchId, cart, reservationId, cartItems };
  const pending = cart.getPendingLines();
  if (pending.length === 0) throw new Error("nothing pending");

  const idempotencyKey = newIdempotencyKey();
  const addingToLocalOrder = isLocalOrder(cart.activeOrderId);

  // Fast path: online, and either a new order or adding to a real server order.
  // (Adding to a still-local order must be queued so the outbox can remap its id.)
  if (typeof navigator !== "undefined" && navigator.onLine && !addingToLocalOrder) {
    try {
      if (cart.activeOrderId) {
        return await apiPost<Order>(
          `/api/cafes/${cafeId}/orders/${cart.activeOrderId}/items`,
          { items: pending },
          { idempotencyKey, offline: "manual" }
        );
      }
      return await apiPost<Order>(
        `/api/cafes/${cafeId}/orders`,
        buildCreateBody(params, pending),
        { idempotencyKey, offline: "manual" }
      );
    } catch (err) {
      // Only fall back to the offline queue on a genuine network failure; a real
      // server/validation error must surface. The same idempotencyKey is reused
      // so the server de-dups if the failed attempt actually reached it.
      if (!isNetworkError(err)) throw err;
    }
  }

  return queueAndBuildLocalOrder(params, cartItems, idempotencyKey);
}

export function orderAmountDue(order: Order): number {
  return Math.max(0, order.total - (order.paidAmount ?? 0));
}

/** True when the order was created locally (offline) and not yet synced. */
export function isLocalOrder(orderId: string | null): boolean {
  return !!orderId?.startsWith("local_");
}
