// 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>Offline 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">OFFLINE 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.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;">
  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">
  ⚠ Saved offline — will sync when online.<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) {
  let response;
  try {
    response = await fetch('/sales/api/orders/add/', {
      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 || `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 {
      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 isConflict = err.isStockConflict || /STOCK_CONFLICT|Insufficient stock/i.test(err.message || '');

      await updateOfflineOrder(localId, {
        status:           isConflict ? 'conflict' : 'pending',
        retries:          retries + 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 (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;

  return {
    items:            cartPayload.items,
    totals:           cartPayload.totals,
    payment_method:   form.querySelector('[name="payment_method"]')?.value  || 'cash',
    payment_status:   form.querySelector('[name="payment_status"]')?.value  || 'full',
    amount_paid:      parseFloat(form.querySelector('[name="amount_paid"]')?.value) || 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   || '',
    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)    window.location.href = result.redirect_url;
          else if (result.order_id)   window.location.href = `/sales/receipt/${result.order_id}/`;
          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;
