/**
 * Service Worker for Inventory Counter PWA
 * Handles offline caching, background sync, and background fetch
 */

const CACHE_VERSION = 'inv-cache-v1';
const RUNTIME_CACHE = 'inv-runtime-v1';
const IMAGE_CACHE = 'inv-images-v1';
const API_CACHE = 'inv-api-v1';

// Files to cache on install (essential app shell)
const CACHE_URLS = [
  '/invcount/',
  '/invcount/index.php',
  '/invcount/login.php',
  '/invcount/offline.html',
  '/invcount/manifest.json',
  '/invcount/assets/css/style.css',
  '/invcount/assets/css/responsive.css',
  '/invcount/assets/js/pwa-helper.js',
  '/invcount/assets/js/sync-manager.js',
  '/invcount/assets/js/notification-handler.js',
];

// ============= SERVICE WORKER INSTALL =============
self.addEventListener('install', (event) => {
  console.log('[SW] Installing Service Worker...');
  
  event.waitUntil(
    caches.open(CACHE_VERSION).then((cache) => {
      console.log('[SW] Caching essential app files...');
      return cache.addAll(CACHE_URLS).catch((err) => {
        console.warn('[SW] Some files failed to cache (may not exist):', err);
        return cache.addAll(
          CACHE_URLS.filter((url) => {
            // Attempt graceful degradation for non-critical files
            return !url.includes('optional');
          })
        );
      });
    }).then(() => {
      console.log('[SW] Cache complete. Claiming clients...');
      return self.skipWaiting();
    })
  );
});

// ============= SERVICE WORKER ACTIVATE =============
self.addEventListener('activate', (event) => {
  console.log('[SW] Activating Service Worker...');
  
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (![CACHE_VERSION, RUNTIME_CACHE, IMAGE_CACHE, API_CACHE].includes(cacheName)) {
            console.log('[SW] Deleting old cache:', cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    }).then(() => {
      console.log('[SW] Claiming all clients...');
      return self.clients.claim();
    })
  );
});

// ============= FETCH EVENT HANDLING =============
self.addEventListener('fetch', (event) => {
  const { request } = event;
  const url = new URL(request.url);

  // Skip non-GET requests
  if (request.method !== 'GET') {
    return;
  }

  // Skip cross-origin requests
  if (url.origin !== self.location.origin) {
    return;
  }

  // Route-specific strategies
  if (url.pathname.includes('/api/')) {
    // API calls: Network-first with cache fallback
    event.respondWith(networkFirstStrategy(request, API_CACHE));
  } else if (url.pathname.match(/\.(jpg|jpeg|png|gif|svg|webp)$/i)) {
    // Images: Cache-first with network fallback
    event.respondWith(cacheFirstStrategy(request, IMAGE_CACHE));
  } else if (url.pathname.match(/\.(css|js)$/)) {
    // Assets: Cache-first with network fallback
    event.respondWith(cacheFirstStrategy(request, RUNTIME_CACHE));
  } else {
    // HTML/PHP pages: Network-first with cache fallback
    event.respondWith(networkFirstStrategy(request, RUNTIME_CACHE));
  }
});

// ============= CACHING STRATEGIES =============

/**
 * Network-first strategy: Try network, fall back to cache
 */
function networkFirstStrategy(request, cacheName) {
  return fetch(request)
    .then((response) => {
      // Cache successful responses
      if (response && response.status === 200) {
        const cache = caches.open(cacheName);
        cache.then((c) => c.put(request, response.clone()));
      }
      return response;
    })
    .catch(() => {
      // Network failed, check cache
      return caches.match(request)
        .then((cached) => {
          if (cached) {
            console.log('[SW] Serving from cache:', request.url);
            return cached;
          }
          // No cache, show offline page
          if (request.destination === 'document') {
            return caches.match('/invcount/offline.html');
          }
          return new Response('Offline', {
            status: 503,
            statusText: 'Service Unavailable',
            headers: new Headers({ 'Content-Type': 'text/plain' }),
          });
        });
    });
}

/**
 * Cache-first strategy: Check cache first, then network
 */
function cacheFirstStrategy(request, cacheName) {
  return caches.match(request)
    .then((cached) => {
      if (cached) {
        // Update cache in background
        fetch(request)
          .then((response) => {
            if (response && response.status === 200) {
              const cache = caches.open(cacheName);
              cache.then((c) => c.put(request, response.clone()));
            }
          })
          .catch(() => {
            // Network error is ok, we already served from cache
          });
        return cached;
      }
      // Not in cache, fetch from network
      return fetch(request)
        .then((response) => {
          if (response && response.status === 200) {
            const cache = caches.open(cacheName);
            cache.then((c) => c.put(request, response.clone()));
          }
          return response;
        })
        .catch(() => {
          return new Response('Offline', {
            status: 503,
            statusText: 'Service Unavailable',
            headers: new Headers({ 'Content-Type': 'text/plain' }),
          });
        });
    });
}

// ============= BACKGROUND SYNC API =============
self.addEventListener('sync', (event) => {
  console.log('[SW] Background Sync triggered:', event.tag);

  if (event.tag === 'sync-counts') {
    event.waitUntil(syncCountData());
  } else if (event.tag === 'sync-assignments') {
    event.waitUntil(syncAssignments());
  } else if (event.tag === 'sync-reports') {
    event.waitUntil(syncReports());
  }
});

async function syncCountData() {
  try {
    console.log('[SW] Syncing count data...');
    const db = await openIndexedDB();
    const pending = await getPendingCounts(db);

    if (pending.length === 0) {
      console.log('[SW] No pending counts to sync');
      return;
    }

    // Send pending counts to server
    const response = await fetch('/invcount/api/sync-counts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ counts: pending }),
    });

    if (!response.ok) throw new Error('Sync failed: ' + response.status);

    // Clear synced data from IndexedDB
    await clearSyncedCounts(db);
    console.log('[SW] Count sync successful');

    // Notify all clients
    notifyClients({
      type: 'sync-complete',
      data: 'Counts synchronized successfully',
    });
  } catch (error) {
    console.error('[SW] Sync error:', error);
    throw error; // Re-throw to trigger retry
  }
}

async function syncAssignments() {
  try {
    console.log('[SW] Syncing assignments...');
    const response = await fetch('/invcount/api/get-assignments');
    if (!response.ok) throw new Error('Failed to sync assignments');

    const data = await response.json();
    const db = await openIndexedDB();
    await saveAssignments(db, data);

    notifyClients({
      type: 'sync-complete',
      data: 'Assignments updated',
    });
  } catch (error) {
    console.error('[SW] Assignment sync error:', error);
    throw error;
  }
}

async function syncReports() {
  try {
    console.log('[SW] Syncing reports...');
    const response = await fetch('/invcount/api/get-reports');
    if (!response.ok) throw new Error('Failed to sync reports');

    const data = await response.json();
    const db = await openIndexedDB();
    await saveReports(db, data);

    notifyClients({
      type: 'sync-complete',
      data: 'Reports updated',
    });
  } catch (error) {
    console.error('[SW] Report sync error:', error);
    throw error;
  }
}

// ============= PERIODIC BACKGROUND SYNC API =============
self.addEventListener('periodicsync', (event) => {
  console.log('[SW] Periodic Sync triggered:', event.tag);

  if (event.tag === 'periodic-sync-counts') {
    event.waitUntil(syncCountData());
  } else if (event.tag === 'periodic-check-notifications') {
    event.waitUntil(checkForNotifications());
  }
});

async function checkForNotifications() {
  try {
    console.log('[SW] Checking for notifications...');
    const response = await fetch('/invcount/api/get-notifications');
    if (!response.ok) return;

    const notifications = await response.json();
    for (const notif of notifications) {
      await self.registration.showNotification(notif.title, {
        body: notif.body,
        icon: notif.icon || '/invcount/assets/images/icons/icon-192x192.png',
        badge: '/invcount/assets/images/icons/badge-72x72.png',
        tag: notif.tag || 'notification',
        data: notif.data || {},
      });
    }
  } catch (error) {
    console.error('[SW] Error checking notifications:', error);
  }
}

// ============= PUSH NOTIFICATION API =============
self.addEventListener('push', (event) => {
  console.log('[SW] Push notification received');

  let notificationData = {
    title: 'Inventory Counter',
    body: 'New notification',
    icon: '/invcount/assets/images/icons/icon-192x192.png',
    badge: '/invcount/assets/images/icons/badge-72x72.png',
  };

  try {
    if (event.data) {
      notificationData = event.data.json();
    }
  } catch (e) {
    notificationData.body = event.data.text();
  }

  event.waitUntil(
    self.registration.showNotification(notificationData.title, {
      body: notificationData.body,
      icon: notificationData.icon,
      badge: notificationData.badge,
      tag: notificationData.tag || 'push-notification',
      data: notificationData.data || {},
      requireInteraction: notificationData.requireInteraction || false,
    })
  );
});

// ============= NOTIFICATION CLICK HANDLING =============
self.addEventListener('notificationclick', (event) => {
  console.log('[SW] Notification clicked:', event.notification.tag);
  event.notification.close();

  const data = event.notification.data || {};
  let urlToOpen = data.url || '/invcount/';

  event.waitUntil(
    clients.matchAll({ type: 'window', includeUncontrolled: true })
      .then((clientList) => {
        // Check if window is already open
        for (let i = 0; i < clientList.length; i++) {
          const client = clientList[i];
          if (client.url === urlToOpen && 'focus' in client) {
            return client.focus();
          }
        }
        // If not open, open new window
        if (clients.openWindow) {
          return clients.openWindow(urlToOpen);
        }
      })
  );
});

// ============= BACKGROUND FETCH API =============
// Note: Background Fetch is triggered via the main thread API
self.addEventListener('backgroundfetchsuccess', (event) => {
  console.log('[SW] Background fetch succeeded:', event.registration.id);
  event.respondWith(updateUI(event.registration));
});

self.addEventListener('backgroundfetchfail', (event) => {
  console.log('[SW] Background fetch failed:', event.registration.id);
  notifyClients({
    type: 'fetch-failed',
    data: `Download "${event.registration.id}" failed`,
  });
});

self.addEventListener('backgroundfetchabort', (event) => {
  console.log('[SW] Background fetch aborted:', event.registration.id);
});

async function updateUI(registration) {
  const records = await registration.matchAll();
  console.log('[SW] Background fetch records:', records.length);
  notifyClients({
    type: 'fetch-success',
    data: `Downloaded ${records.length} files`,
  });
}

// ============= UTILITY FUNCTIONS =============

/**
 * Open IndexedDB for local data storage
 */
function openIndexedDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('InvCounterDB', 1);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;
      if (!db.objectStoreNames.contains('counts')) {
        db.createObjectStore('counts', { keyPath: 'id' });
      }
      if (!db.objectStoreNames.contains('assignments')) {
        db.createObjectStore('assignments', { keyPath: 'id' });
      }
      if (!db.objectStoreNames.contains('reports')) {
        db.createObjectStore('reports', { keyPath: 'id' });
      }
    };
  });
}

/**
 * Get pending counts from IndexedDB
 */
function getPendingCounts(db) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction('counts', 'readonly');
    const store = tx.objectStore('counts');
    const request = store.getAll();

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);
  });
}

/**
 * Clear synced counts from IndexedDB
 */
function clearSyncedCounts(db) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction('counts', 'readwrite');
    const store = tx.objectStore('counts');
    const request = store.clear();

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve();
  });
}

/**
 * Save assignments to IndexedDB
 */
function saveAssignments(db, assignments) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction('assignments', 'readwrite');
    const store = tx.objectStore('assignments');
    const request = store.clear();

    request.onsuccess = () => {
      assignments.forEach((assignment) => {
        store.add(assignment);
      });
      tx.oncomplete = () => resolve();
    };
    request.onerror = () => reject(request.error);
  });
}

/**
 * Save reports to IndexedDB
 */
function saveReports(db, reports) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction('reports', 'readwrite');
    const store = tx.objectStore('reports');
    const request = store.clear();

    request.onsuccess = () => {
      reports.forEach((report) => {
        store.add(report);
      });
      tx.oncomplete = () => resolve();
    };
    request.onerror = () => reject(request.error);
  });
}

/**
 * Notify all connected clients
 */
function notifyClients(message) {
  return self.clients.matchAll().then((clients) => {
    clients.forEach((client) => {
      client.postMessage(message);
    });
  });
}
