// static/js/order-sync.js — SwiftPOS Offline Order Engine v2
// Depends on: product-cache.js (swiftposOpenDB)
// Features:
//   • Offline-first order queue in IndexedDB
//   • Instant printable offline receipt on submit
//   • Auto-sync queued orders when back online
//   • Exponential back-off retry
//   • Per-order status tracking (pending | synced | conflict | failed)
//   • Duplicate prevention via client_id

'use strict';

/* ─── Toast / Alert helper ─────────────────────────────── */
function swiftposAlert(msg, type = 'warning') {
  // Try SwiftPOS toast if available, else console-fallback
  const container = document.querySelector('.swiftpos-toast-container');
  if (container) {
    const div = document.createElement('div');
    div.className = `swiftpos-toast swiftpos-toast-${type}`;
    div.innerHTML = `
      <div class="toast-icon">
        <i class="fas ${type === 'success' ? 'fa-check-circle' : type === 'error' || type === 'danger' ? 'fa-exclamation-circle' : 'fa-exclamation-triangle'}"></i>
      </div>
      <div class="toast-text">${msg}</div>`;
    container.appendChild(div);
    setTimeout(() => div.remove(), 6000);
  } else {
    // fallback
    console.warn('[SwiftPOS]', msg);
    if (type === 'error' || type === 'danger') alert(msg);
  }
}

/* ─── DB helpers ───────────────────────────────────────── */
let ordersDB = null;

async function getOrdersDB() {
  if (ordersDB) return ordersDB;
  if (!window.swiftposOpenDB) throw new Error('swiftposOpenDB not found. Load product-cache.js first.');
  ordersDB = await window.swiftposOpenDB();
  return ordersDB;
}

/* ─── Offline order store ──────────────────────────────── */
async function saveOfflineOrder(orderPayload) {
  const db = await getOrdersDB();
  return new Promise((resolve, reject) => {
    const tx    = db.transaction('offline_orders', 'readwrite');
    const store = tx.objectStore('offline_orders');
    const localId = `offline-${Date.now()}-${Math.random().toString(16).slice(2)}`;
    const record = {
      id:         localId,
      status:     'pending',
      payload:    orderPayload,
      retries:    0,
      created_at: new Date().toISOString(),
      last_error: null,
    };
    const req      = store.put(record);
    req.onsuccess  = () => resolve(localId);
    req.onerror    = () => reject(req.error);
  });
}

async function getPendingOfflineOrders() {
  const db = await getOrdersDB();
  return new Promise(resolve => {
    const tx  = db.transaction('offline_orders', 'readonly');
    const req = tx.objectStore('offline_orders').getAll();
    req.onsuccess = () => resolve((req.result || []).filter(r => r.status === 'pending'));
  });
}

async function updateOfflineOrder(localId, updates) {
  const db = await getOrdersDB();
  return new Promise(resolve => {
    const tx    = db.transaction('offline_orders', 'readwrite');
    const store = tx.objectStore('offline_orders');
    const get   = store.get(localId);
    get.onsuccess = () => {
      const rec = get.result;
      if (!rec) return resolve();
      Object.assign(rec, updates);
      store.put(rec);
      tx.oncomplete = () => resolve();
    };
  });
}

/* ─── Offline Receipt ──────────────────────────────────── */
function buildOfflineReceiptHTML(order) {
  const sym  = window.swiftposCurrencySymbol || '₦';
  const dp   = window.swiftposDecimalPlaces  || 2;
  const biz  = window.swiftposBusinessName   || 'SwiftPOS';
  const now  = new Date().toLocaleString();

  const rows = (order.items || []).map(i => `
    <tr>
      <td style="padding:4px 0;">${i.name}</td>
      <td style="text-align:center;">${i.quantity}</td>
      <td style="text-align:right;">${sym}${Number(i.price).toFixed(dp)}</td>
      <td style="text-align:right;">${sym}${(i.price * i.quantity).toFixed(dp)}</td>
    </tr>`).join('');

  const totals = order.totals || {};
  const grand  = totals.grandTotal || (order.items || []).reduce((s,i) => s + i.price * i.quantity, 0);

  return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Receipt — ${biz}</title>
<style>
  body { font-family: 'Courier New', monospace; font-size: 13px; max-width: 300px; margin: 0 auto; padding: 16px; }
  h2   { text-align: center; font-size: 16px; margin: 0 0 4px; }
  .sub { text-align: center; color: #666; font-size: 11px; margin-bottom: 12px; }
  hr   { border: none; border-top: 1px dashed #aaa; margin: 8px 0; }
  table { width: 100%; border-collapse: collapse; }
  th   { font-size: 11px; text-transform: uppercase; color: #888; padding-bottom: 4px; }
  .footer { text-align: center; color: #999; font-size: 10px; margin-top: 12px; }
  .offline-notice { background:#fff3cd; padding:6px 10px; border-radius:6px; font-size:11px; color:#7c5200; text-align:center; margin-top:10px; }
  @media print { .offline-notice { display: none; } }
</style>
</head>
<body>
<h2>${biz}</h2>
<div class="sub">RECEIPT<br>${now}</div>
${order.customer_name ? `<div style="text-align:center;font-size:12px;">Customer: <strong>${order.customer_name}</strong></div>` : ''}
<hr>
<table>
  <thead>
    <tr><th>Item</th><th style="text-align:center;">Qty</th><th style="text-align:right;">Price</th><th style="text-align:right;">Total</th></tr>
  </thead>
  <tbody>${rows}</tbody>
</table>
<hr>
<table>
  <tr><td>Sub-total</td><td style="text-align:right;">${sym}${Number(totals.subtotal || grand).toFixed(dp)}</td></tr>
  ${totals.discount ? `<tr><td>Discount</td><td style="text-align:right;">-${sym}${Number(totals.discount).toFixed(dp)}</td></tr>` : ''}
  ${totals.tax ? `<tr><td>VAT</td><td style="text-align:right;">${sym}${Number(totals.tax).toFixed(dp)}</td></tr>` : ''}
  <tr><td><strong>TOTAL</strong></td><td style="text-align:right;"><strong>${sym}${Number(grand).toFixed(dp)}</strong></td></tr>
</table>
<hr>
<div style="font-size:12px;">
  ${
    order.is_split_payment && order.split_payments && order.split_payments.length
      ? `<strong>Payment: Split</strong><br>` +
        order.split_payments.map(p =>
          `&nbsp;&nbsp;└ ${p.method.toUpperCase()}: ${sym}${Number(p.amount).toFixed(dp)}`
        ).join('<br>')
      : `Payment: <strong>${order.payment_method || 'Cash'}</strong><br>
         Status: <strong>${order.payment_status === 'full' ? 'Paid in Full' : order.payment_status === 'partial' ? 'Partial' : 'Credit'}</strong>`
  }
</div>
<div class="offline-notice">
  ⚠ -- <br>Ref: ${order.client_id || '—'}
</div>
<div class="footer">Thank you for your business!</div>
</body>
</html>`;
}

function showOfflineReceipt(order) {
  const html = buildOfflineReceiptHTML(order);
  const win  = window.open('', '_blank', 'width=400,height=600,scrollbars=yes');
  if (win) {
    win.document.write(html);
    win.document.close();
    win.focus();
    setTimeout(() => win.print(), 500);
  } else {
    // fallback: create a blob download
    const blob = new Blob([html], { type: 'text/html' });
    const url  = URL.createObjectURL(blob);
    const a    = Object.assign(document.createElement('a'), { href: url, download: 'offline-receipt.html' });
    a.click();
    URL.revokeObjectURL(url);
  }
}

/* ─── Show pending orders badge ────────────────────────── */
async function updatePendingBadge() {
  try {
    const pending = await getPendingOfflineOrders();
    const badge   = document.getElementById('gpos-offline-pending-count');
    if (badge) { badge.textContent = pending.length; badge.style.display = pending.length ? '' : 'none'; }
    const banner = document.getElementById('gpos-offline-banner');
    if (banner) banner.classList.toggle('d-none', navigator.onLine && pending.length === 0);
  } catch (_) { /* ignore */ }
}

/* ─── Network / API ────────────────────────────────────── */
function getCSRFToken() {
  for (const c of document.cookie.split(';')) {
    const [k, v] = c.trim().split('=');
    if (k === 'csrftoken') return decodeURIComponent(v);
  }
  return null;
}

async function sendOrderToServer(orderPayload) {
  const url = window.swiftposOrdersApiUrl;
  if (!url) {
    throw new Error('swiftposOrdersApiUrl is not set. Cannot submit order.');
  }
  let response;
  try {
    response = await fetch(url, {
      method:      'POST',
      headers:     { 'Content-Type': 'application/json', 'X-CSRFToken': getCSRFToken() || '' },
      body:        JSON.stringify(orderPayload),
      credentials: 'same-origin',
    });
  } catch (err) {
    err.isNetworkError = true;
    throw err;
  }

  let data = null;
  try { data = await response.json(); } catch (_) { /* ignore */ }

  if (!response.ok || data?.success === false) {
    const msg   = data?.error || (data ? JSON.stringify(data) : `HTTP ${response.status}`);
    const error = new Error(data?.code ? `${data.code}:${msg}` : msg);
    if (data?.code === 'STOCK_CONFLICT') error.isStockConflict = true;
    throw error;
  }
  return data;
}

/* ─── Sync engine ──────────────────────────────────────── */
let _syncInProgress = false;

async function syncPendingOrders() {
  if (_syncInProgress || !navigator.onLine) return;
  _syncInProgress = true;

  let pending;
  try { pending = await getPendingOfflineOrders(); }
  catch (err) { console.error('[SwiftPOS] Error loading pending orders', err); _syncInProgress = false; return; }

  if (!pending.length) { _syncInProgress = false; return; }

  console.log(`[SwiftPOS] Syncing ${pending.length} offline orders…`);

  for (const record of pending) {
    const localId = record.id;
    const retries = record.retries || 0;

    // Exponential back-off: skip if last attempt was too recent
    if (record.last_attempt_at && retries > 0) {
      const wait  = Math.min(30000, 2000 * Math.pow(2, retries - 1));
      const since = Date.now() - new Date(record.last_attempt_at).getTime();
      if (since < wait) continue;
    }

    try {
      // Repair legacy payloads that might be stuck in the queue
      if (record.payload && record.payload.items) {
        record.payload.items = record.payload.items.map(i => {
          const rawId    = String(i.productId || i.product_id || i.id || '');
          const parts    = rawId.split('-');
          const productId  = parseInt(parts[0], 10);
          const variantId  = parts.length === 2 ? parseInt(parts[1], 10) : (i.variantId || i.variant_id || null);
          return {
            product_id: productId,
            variant_id: variantId || null,
            quantity:   parseFloat(i.quantity) || 1,
            discount:   parseFloat(i.discount || 0),
            price:      parseFloat(i.price || i.basePrice || i.unit_price || 0),
            name:       i.name || i.product_name || 'Unknown item',
          };
        });
      }
      if (record.payload && record.payload.amount_paid === null) {
          record.payload.amount_paid = 0;
      }

      const result = await sendOrderToServer(record.payload);
      await updateOfflineOrder(localId, { status: 'synced', synced_at: new Date().toISOString(), server_response: result });
      console.log('[SwiftPOS] Synced offline order', localId);
      swiftposAlert(`Offline order synced successfully!`, 'success');
    } catch (err) {
      console.error('[SwiftPOS] Sync failed', localId, err.message);
      const retriesNum = record.retries || 0;
      const isConflict = err.isStockConflict || /STOCK_CONFLICT|Insufficient stock/i.test(err.message || '');
      
      let nextStatus = isConflict ? 'conflict' : 'pending';
      if (!isConflict && retriesNum >= 3) {
          nextStatus = 'failed';
          console.error('[SwiftPOS] Abandoning order ' + localId + ' after ' + retriesNum + ' retries due to persistent error: ' + err.message);
      }

      await updateOfflineOrder(localId, {
        status:           nextStatus,
        retries:          retriesNum + 1,
        last_error:       err.message || 'Unknown error',
        last_attempt_at:  new Date().toISOString(),
      });

      if (isConflict) {
        swiftposAlert('An offline order failed to sync — stock may be insufficient. Check inventory.', 'error');
      } else if (nextStatus === 'failed') {
        swiftposAlert('An offline order persistently failed to sync and was aborted. Please check API logs.', 'error');
      } else if (err.isNetworkError) {
        break; // network gone again, stop retrying for now
      }
    }
  }

  _syncInProgress = false;
  await updatePendingBadge();
}

/* ─── Collect form data ────────────────────────────────── */
function collectOrderFromForm(form) {
  const payloadField = document.getElementById('cart-payload-field');
  let cartPayload = null;
  try { cartPayload = payloadField?.value ? JSON.parse(payloadField.value) : null; } catch (_) {}

  if (!cartPayload?.items?.length) return null;

  // POS V1: promo code comes from window globals set by pos-cart.js applyPromoCode
  // Grid V2: comes from window.gposPromoCode / window.gposPromoDiscount
  const promoCode     = window._posPromoCode     || window.gposPromoCode     || '';
  const promoDiscount = window._posPromoDiscount  || window.gposPromoDiscount || 0;

  // Also check if Grid V2 stores discount in the payload totals
  const discountAmount = promoDiscount || (cartPayload.totals && cartPayload.totals.discount) || 0;

  const mappedItems = cartPayload.items.map(i => {
    // Cart stores variant items as productId = "parentProductId-variantId" e.g. "12-4".
    // Plain products just use the numeric product id e.g. "12" or 12.
    const rawId    = String(i.productId || i.product_id || i.id || '');
    const parts    = rawId.split('-');
    const productId  = parseInt(parts[0], 10);
    const variantId  = parts.length === 2 ? parseInt(parts[1], 10) : (i.variantId || i.variant_id || null);

    return {
      product_id: productId,
      variant_id: variantId || null,
      quantity:   parseFloat(i.quantity) || 1,
      discount:   parseFloat(i.discount || 0),
      price:      parseFloat(i.price || i.basePrice || i.unit_price || 0),
      name:       i.name || i.product_name || 'Unknown item',
    };
  });

  const paymentStatus  = form.querySelector('[name="payment_status"]')?.value  || 'full';
  const amountPaidRaw  = form.querySelector('[name="amount_paid"]')?.value;
  // Only send a real number if user typed something; null tells server to infer from status
  const amountPaidNum  = (amountPaidRaw !== undefined && amountPaidRaw !== null && amountPaidRaw !== '')
                           ? parseFloat(amountPaidRaw)
                           : null;

  // Split payment
  const splitToggleEl = form.querySelector('[name="is_split_payment"]');
  const isSplit = splitToggleEl ? splitToggleEl.checked : false;
  let splitPayments = null;
  if (isSplit) {
    const m1 = form.querySelector('[name="split_method_1"]')?.value || 'cash';
    const m2 = form.querySelector('[name="split_method_2"]')?.value || 'pos';
    const a1 = parseFloat(form.querySelector('[name="split_amount_1"]')?.value || '0') || 0;
    const a2 = parseFloat(form.querySelector('[name="split_amount_2"]')?.value || '0') || 0;
    // Only treat as split if both amounts are non-zero
    if (a1 > 0 && a2 > 0) {
      splitPayments = [
        { method: m1, amount: a1 },
        { method: m2, amount: a2 },
      ];
    }
  }
  // If toggle was on but amounts were empty, downgrade to normal payment
  const effectivelySplit = isSplit && splitPayments !== null;

  return {
    items:            mappedItems,
    totals:           cartPayload.totals,
    payment_method:   effectivelySplit ? 'split' : (form.querySelector('[name="payment_method"]')?.value || 'cash'),
    payment_status:   paymentStatus,
    amount_paid:      (amountPaidNum !== null && !isNaN(amountPaidNum)) ? amountPaidNum : null,
    reference_number: form.querySelector('[name="reference_number"]')?.value || '',
    customer_name:    form.querySelector('[name="customer_name"]')?.value    || '',
    customer_phone:   form.querySelector('[name="customer_phone"]')?.value   || '',
    promo_code:       promoCode,
    discount_amount:  parseFloat(discountAmount) || 0,
    is_split_payment: effectivelySplit,
    split_payments:   splitPayments,
    created_at:       new Date().toISOString(),
    client_id:        `sw-${Date.now()}-${Math.random().toString(16).slice(2)}`,
  };
}

/* ─── Hook POS form submit ─────────────────────────────── */
function setupOrderSubmit() {
  const form = document.getElementById('pos-form');
  if (!form) return;

  form.addEventListener('submit', async function (e) {
    e.preventDefault();
    const submitBtn = form.querySelector('[type="submit"]');
    if (submitBtn) submitBtn.disabled = true;

    const orderPayload = collectOrderFromForm(form);
    if (!orderPayload) {
      swiftposAlert('No items in cart. Add products first.', 'warning');
      if (submitBtn) submitBtn.disabled = false;
      return;
    }

    const isOnline = navigator.onLine;

    try {
      if (isOnline) {
        try {
          // ── ONLINE: submit directly ──────────────────
          const result = await sendOrderToServer(orderPayload);
          if (window.swiftposCart?.clearCart) await window.swiftposCart.clearCart();

          if (!result.redirect_url && result.id) {
             const _slug = window.swiftposCompanySlug || '';
             result.redirect_url = (_slug ? '/' + _slug : '') + '/sales/receipt/' + result.id + '/';
          }

          if (result.redirect_url || result.order_number) {
            if (typeof window.swiftposOnOrderComplete === 'function') {
              window.swiftposOnOrderComplete(result);
            } else if (result.redirect_url) {
              window.location.href = result.redirect_url;
            }
          } else swiftposAlert('Order completed. No receipt URL provided.', 'warning');

        } catch (err) {
          if (err.isNetworkError || /Failed to fetch/i.test(err.message || '')) {
            // Treat as offline even though navigator.onLine was true
            goto_offline:
            {
              const localId = await saveOfflineOrder(orderPayload);
              if (window.swiftposCart?.clearCart) await window.swiftposCart.clearCart();
              showOfflineReceipt(orderPayload);
              swiftposAlert('No internet — order saved offline and will sync automatically.', 'warning');
              await updatePendingBadge();
              break goto_offline;
            }
          } else {
            throw err;
          }
        }
      } else {
        // ── OFFLINE: queue + show receipt ─────────────
        const localId = await saveOfflineOrder(orderPayload);
        if (window.swiftposCart?.clearCart) await window.swiftposCart.clearCart();
        showOfflineReceipt(orderPayload);
        swiftposAlert('Offline mode — order queued &amp; receipt ready to print. Will sync when online.', 'warning');
        await updatePendingBadge();
        form.reset();
      }
    } catch (err) {
      console.error('[SwiftPOS] Submit error', err);
      swiftposAlert('Error submitting order: ' + (err.message || String(err)), 'error');
    } finally {
      if (submitBtn) submitBtn.disabled = false;
    }
  });
}

/* ─── Wiring ────────────────────────────────────────────── */
document.addEventListener('DOMContentLoaded', () => {
  setupOrderSubmit();
  syncPendingOrders();
  updatePendingBadge();
});

window.addEventListener('online',  () => { syncPendingOrders(); updatePendingBadge(); });
window.addEventListener('offline', () => updatePendingBadge());

// Public API
window.swiftposSyncOrders   = syncPendingOrders;
window.swiftposOfflineReceipt = showOfflineReceipt;
