import db from '../db/posdb'
import client from '../api/client'

// ─── Helpers ──────────────────────────────────────────────────────────────────

function generateUUID() {
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID()
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = (Math.random() * 16) | 0
    return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
  })
}

function offlineItemId() {
  return `offline_item_${generateUUID()}`
}

// ─── Queue an offline order creation ─────────────────────────────────────────
// Writes a 'create_order' op into the unified offline_ops queue (sequenced).
// Also writes a synthetic local order to pos_orders so the table card shows it.

export async function queueOfflineOrder({ tableId, items, waiterId }) {
  const uuid = generateUUID()
  const localOrderId = `offline_${uuid}`
  const offlineAt = new Date().toISOString()
  const seq = Date.now()

  // Write sequenced op
  await db.offline_ops.add({
    type: 'create_order',
    uuid,
    localOrderId,
    tableId,
    items,
    waiterId,
    offlineAt,
    seq,
    synced: 0,
    serverId: null,
  })

  // Also write to legacy offline_orders so pendingOrderCount() still works
  const localId = await db.offline_orders.add({
    uuid,
    tableId,
    items,
    waiterId,
    offlineAt,
    synced: 0,
    serverId: null,
  })

  // Synthetic local order for immediate UI display
  const syntheticOrder = {
    id: localOrderId,
    table_id: tableId,
    status: 'open',
    opened_at: offlineAt,
    waiter_ids: waiterId ? [waiterId] : [],
    items: items.map((item) => ({
      id: offlineItemId(),
      product_id: item.product_id,
      product: item._productSnapshot || null,
      quantity: item.quantity,
      unit_price: item._unitPrice || 0,
      unit_type: item._unitType || null,
      status: 'active',
      kds_status: 'pending',
      selected_options: item.selected_options || [],
      removed_ingredients: item.removed_ingredients || [],
      notes: item.notes || '',
    })),
    payments: [],
    _isOffline: true,
    _localId: localId,
  }
  await db.pos_orders.put(syntheticOrder)

  return { uuid, localId, syntheticOrderId: localOrderId }
}

// ─── Queue an offline split ───────────────────────────────────────────────────

export async function queueOfflineSplit({ orderId, itemId, quantity }) {
  const order = await db.pos_orders.get(orderId)
  if (!order) throw new Error('order not found in IDB')

  const origItem = order.items.find(i => String(i.id) === String(itemId))
  if (!origItem) throw new Error('item not found in order')

  const splitQty = quantity
  const remainQty = origItem.quantity - splitQty
  if (remainQty <= 0) throw new Error('split quantity must be less than item quantity')

  const newItemId = offlineItemId()
  const seq = Date.now()

  const updatedItems = order.items.map(i =>
    String(i.id) === String(itemId) ? { ...i, quantity: remainQty } : i
  )
  const splitItem = { ...origItem, id: newItemId, quantity: splitQty }
  updatedItems.push(splitItem)

  await db.pos_orders.put({ ...order, items: updatedItems })

  await db.offline_ops.add({
    type: 'split',
    uuid: generateUUID(),
    localOrderId: String(orderId),
    orderId: String(orderId),
    itemId: String(itemId),
    quantity: splitQty,
    localNewItemId: newItemId,
    seq,
    synced: 0,
  })

  return { reducedItem: { ...origItem, quantity: remainQty }, newItem: splitItem }
}

// ─── Queue an offline payment ─────────────────────────────────────────────────
// Works for BOTH real server orders and offline-created orders.
// Everything goes into offline_ops so it's sequenced correctly.

export async function queueOfflinePayment({ orderId, itemIds, paymentMethod }) {
  const uuid = generateUUID()
  const offlineAt = new Date().toISOString()
  const seq = Date.now()

  const allLocal = itemIds.every(id => String(id).startsWith('offline_item_'))
  const allReal  = itemIds.every(id => typeof id === 'number' || !String(id).startsWith('offline_item_'))

  if (allReal) {
    // Pure real IDs — can also go to legacy offline_payments for compat,
    // but put in offline_ops first so ordering is respected
    await db.offline_ops.add({
      type: 'pay_real',
      uuid,
      localOrderId: String(orderId),
      orderId: String(orderId),
      itemIds,
      paymentMethod,
      offlineAt,
      seq,
      synced: 0,
    })
  } else {
    // Contains offline item IDs (from an offline-created order or offline split)
    await db.offline_ops.add({
      type: 'pay',
      uuid,
      localOrderId: String(orderId),
      orderId: String(orderId),
      localItemIds: itemIds.filter(id => String(id).startsWith('offline_item_')),
      realItemIds: itemIds.filter(id => !String(id).startsWith('offline_item_')),
      paymentMethod,
      offlineAt,
      seq,
      synced: 0,
    })
  }

  // Optimistic local update
  const order = await db.pos_orders.get(orderId)
  if (order) {
    const updatedItems = order.items.map(i =>
      itemIds.some(id => String(id) === String(i.id)) ? { ...i, status: 'paid' } : i
    )
    const allPaid = updatedItems.filter(i => i.status === 'active').length === 0
    await db.pos_orders.put({
      ...order,
      items: updatedItems,
      status: allPaid ? 'paid' : 'partially_paid',
    })
  }

  return uuid
}

// ─── Unified flush — processes ALL offline ops in strict seq order ─────────────
//
// idMap tracks localId → real server id for both orders and items.
// Each op resolves its dependencies through idMap before calling the server.

export async function flushAllOfflineOps() {
  // Drain legacy offline_orders first (pre-v0.6.4 records, no seq)
  try {
    await _flushLegacyOfflineOrders()
  } catch (e) {
    console.warn('[offline] legacy order drain failed:', e?.message)
  }

  // Load all unsynced ops sorted by seq
  const pending = (await db.offline_ops.toArray())
    .filter(op => !op.synced)
    .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0))

  // localId → real server id (orders and items)
  const idMap = {}

  for (const op of pending) {
    try {
      if (op.type === 'create_order') {
        await _flushCreateOrder(op, idMap)
      } else if (op.type === 'split') {
        await _flushSplit(op, idMap)
      } else if (op.type === 'pay' || op.type === 'pay_real') {
        await _flushPay(op, idMap)
      }
    } catch (e) {
      console.warn('[offline] op failed, will retry:', op.type, op.uuid, e?.message)
    }
  }

  // Drain legacy offline_payments (real-ID payments queued before v0.6.4)
  try {
    await _flushLegacyOfflinePayments()
  } catch (e) {
    console.warn('[offline] legacy payment drain failed:', e?.message)
  }
}

// ── Legacy drains (backward compat) ──────────────────────────────────────────

async function _flushLegacyOfflineOrders() {
  const pending = (await db.offline_orders.toArray()).filter(o => !o.synced)
  if (pending.length === 0) return

  // Load all offline_ops uuids for dedup check (JS filter — uuid index may not exist on old DBs)
  const allOps = await db.offline_ops.toArray()
  const opUuids = new Set(allOps.map(op => op.uuid))

  for (const entry of pending) {
    // Skip if a create_order op with same uuid is in offline_ops (handled there)
    if (opUuids.has(entry.uuid)) {
      // Mark legacy record synced — the op queue handles it
      await db.offline_orders.update(entry.localId, { synced: 1 })
      continue
    }
    // True legacy record — handle directly
    try {
      let orderId = null
      try {
        const statusRes = await client.get(`/api/tables/${entry.tableId}/status`)
        orderId = statusRes.data.active_order_id ?? null
      } catch { /* offline */ }

      if (!orderId) {
        const createRes = await client.post('/api/orders/', { table_id: entry.tableId })
        orderId = createRes.data?.id
        if (!orderId) throw new Error('no order id returned')
      }

      await client.post(`/api/orders/${orderId}/items`, {
        items: entry.items.map(item => ({
          product_id: item.product_id,
          quantity: item.quantity,
          selected_options: item.selected_options || [],
          removed_ingredients: item.removed_ingredients || [],
          notes: item.notes || '',
        })),
      })

      const fullRes = await client.get(`/api/orders/${orderId}`)
      await db.offline_orders.update(entry.localId, { synced: 1, serverId: orderId })
      await db.pos_orders.delete(`offline_${entry.uuid}`)
      await db.pos_orders.put({
        ...fullRes.data,
        waiter_ids: fullRes.data.waiters?.map(w => w.waiter_id) ?? [],
      })
    } catch { /* leave pending */ }
  }
}

async function _flushLegacyOfflinePayments() {
  const all = await db.offline_payments.toArray()
  const pending = all.filter(p => !p.synced)
  for (const payment of pending) {
    try {
      const res = await client.post(`/api/orders/${payment.orderId}/pay-offline`, {
        uuid: payment.uuid,
        item_ids: payment.itemIds,
        payment_method: payment.paymentMethod,
        offline_at: payment.offlineAt,
      })
      await db.offline_payments.update(payment.localId, {
        synced: 1,
        isDuplicate: res.data.is_duplicate ? 1 : 0,
      })
    } catch { /* leave pending */ }
  }
}

// ── Op handlers ───────────────────────────────────────────────────────────────

async function _flushCreateOrder(op, idMap) {
  // Already synced by legacy drain? Check idMap or look for real order
  let orderId = null
  try {
    const statusRes = await client.get(`/api/tables/${op.tableId}/status`)
    orderId = statusRes.data.active_order_id ?? null
  } catch { /* offline */ }

  if (!orderId) {
    const createRes = await client.post('/api/orders/', { table_id: op.tableId })
    orderId = createRes.data?.id
    if (!orderId) throw new Error('no order id returned')
  }

  await client.post(`/api/orders/${orderId}/items`, {
    items: op.items.map(item => ({
      product_id: item.product_id,
      quantity: item.quantity,
      selected_options: item.selected_options || [],
      removed_ingredients: item.removed_ingredients || [],
      notes: item.notes || '',
    })),
  })

  // Capture synthetic order item ids BEFORE deleting it
  const syntheticOrder = await db.pos_orders.get(op.localOrderId)
  const syntheticItemIds = syntheticOrder?.items?.map(i => i.id) ?? []

  // Fetch full hydrated order
  const fullRes = await client.get(`/api/orders/${orderId}`)
  const serverOrder = fullRes.data

  // Map localOrderId → real server order id
  idMap[op.localOrderId] = orderId

  // Map offline item ids → real item ids by position (last N added server items)
  if (syntheticItemIds.length > 0 && serverOrder.items) {
    const addedServerItems = serverOrder.items.slice(-syntheticItemIds.length)
    syntheticItemIds.forEach((localItemId, idx) => {
      if (addedServerItems[idx]) {
        idMap[localItemId] = addedServerItems[idx].id
      }
    })
  }

  // Mark legacy offline_orders record synced too
  await db.offline_orders.where('uuid').equals(op.uuid).modify({ synced: 1, serverId: orderId })

  await db.offline_ops.update(op.localId, { synced: 1, serverId: orderId })

  // Replace synthetic order in IDB with real server order
  await db.pos_orders.delete(op.localOrderId)
  await db.pos_orders.put({
    ...serverOrder,
    waiter_ids: serverOrder.waiters?.map(w => w.waiter_id) ?? [],
  })
}

async function _flushSplit(op, idMap) {
  const realOrderId = idMap[op.localOrderId] ?? op.orderId
  if (String(realOrderId).startsWith('offline_')) {
    // Parent order not yet synced — skip, will retry
    throw new Error('parent order not yet synced')
  }

  const realItemId = idMap[op.itemId] ?? op.itemId
  if (String(realItemId).startsWith('offline_item_')) {
    throw new Error('source item not yet resolved')
  }

  const res = await client.post(
    `/api/orders/${realOrderId}/items/${realItemId}/split`,
    { quantity: op.quantity }
  )
  const newItem = res.data.find(i => i.id !== Number(realItemId))
  if (newItem) idMap[op.localNewItemId] = newItem.id

  await db.offline_ops.update(op.localId, { synced: 1 })
}

async function _flushPay(op, idMap) {
  // Resolve order id
  const realOrderId = idMap[op.localOrderId] ?? op.orderId
  if (String(realOrderId).startsWith('offline_')) {
    throw new Error('parent order not yet synced')
  }

  if (op.type === 'pay_real') {
    // All real item ids — straightforward
    await client.post(`/api/orders/${realOrderId}/pay-offline`, {
      uuid: op.uuid,
      item_ids: op.itemIds,
      payment_method: op.paymentMethod,
      offline_at: op.offlineAt,
    })
    await db.offline_ops.update(op.localId, { synced: 1 })
    return
  }

  // Resolve local item ids
  const resolvedLocal = (op.localItemIds || []).map(lid => idMap[lid] ?? lid)
  const stillLocal = resolvedLocal.filter(id => String(id).startsWith('offline_item_'))
  if (stillLocal.length > 0) {
    throw new Error('offline item ids not yet resolved')
  }

  const allIds = [...resolvedLocal, ...(op.realItemIds || [])]
  await client.post(`/api/orders/${realOrderId}/pay-offline`, {
    uuid: op.uuid,
    item_ids: allIds,
    payment_method: op.paymentMethod,
    offline_at: op.offlineAt,
  })
  await db.offline_ops.update(op.localId, { synced: 1 })
}

// ─── Kept for SSEContext compat ───────────────────────────────────────────────

export async function flushOfflineOrders() {
  // no-op — handled by flushAllOfflineOps
}

export async function flushOfflineOps() {
  // no-op — handled by flushAllOfflineOps
}

export async function pendingOrderCount() {
  const ops = await db.offline_ops.toArray()
  return ops.filter(o => !o.synced).length
}
