/**
 * Service Worker Generator
 * 
 * PWA service worker generator with caching strategies
 * Extracted from Holy Habit service worker implementation
 */

import { ServiceWorkerConfig, CacheStrategy, ServiceWorkerError } from '../types/PWA';

export class ServiceWorkerGenerator {
  private config: ServiceWorkerConfig;

  constructor(config: ServiceWorkerConfig) {
    this.config = config;
    this.validateConfig();
  }

  /**
   * Generate complete service worker JavaScript code
   * 
   * @returns Service worker JavaScript code as string
   */
  generate(): string {
    const parts = [
      this.generateHeader(),
      this.generateCacheConfiguration(),
      this.generateInstallEvent(),
      this.generateActivateEvent(),
      this.generateFetchEvent(),
      this.generateSyncEvent(),
      this.generatePushNotificationEvents(),
      this.generateUpdateHandling(),
      this.generateUtilityFunctions()
    ];

    return parts.join('\n\n');
  }

  /**
   * Generate service worker header with version and cache configuration
   */
  private generateHeader(): string {
    return `// Service Worker generated by @mvp-factory/holy-pwa
// Version: ${this.config.version}
// Cache Name: ${this.config.cacheName}

const CACHE_VERSION = '${this.config.version}';
const CACHE_NAME = \`${this.config.cacheName}-v\${CACHE_VERSION}\`;`;
  }

  /**
   * Generate cache configuration
   */
  private generateCacheConfiguration(): string {
    const urlsToCache = this.config.urlsToCache.map(url => `  '${url}'`).join(',\n');
    
    return `// URLs to cache on install
const urlsToCache = [
${urlsToCache}
];`;
  }

  /**
   * Generate install event handler
   */
  private generateInstallEvent(): string {
    const skipWaiting = this.config.skipWaiting ? '\n      .then(() => self.skipWaiting())' : '';

    return `// Install event - cache resources
self.addEventListener('install', event => {
  console.log('Service Worker: Installing...');
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('Service Worker: Caching files');
        return cache.addAll(urlsToCache);
      })${skipWaiting}
      .catch(error => {
        console.error('Service Worker: Install failed', error);
      })
  );
});`;
  }

  /**
   * Generate activate event handler
   */
  private generateActivateEvent(): string {
    const clientsClaim = this.config.clientsClaim ? '\n    }).then(() => self.clients.claim())' : '';

    return `// Activate event - clean up old caches
self.addEventListener('activate', event => {
  console.log('Service Worker: Activating...');
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            console.log('Service Worker: Deleting old cache:', cacheName);
            return caches.delete(cacheName);
          }
        })
      );${clientsClaim}
    })
  );
});`;
  }

  /**
   * Generate fetch event handler with caching strategies
   */
  private generateFetchEvent(): string {
    return `// Fetch event - implement caching strategies
self.addEventListener('fetch', event => {
  const requestUrl = new URL(event.request.url);
  
  // Skip non-GET requests
  if (event.request.method !== 'GET') {
    return;
  }

  ${this.generateCacheStrategies()}

  // Default: Network first, cache fallback
  event.respondWith(
    fetch(event.request)
      .then(response => {
        // Cache successful responses
        if (response && response.status === 200 && response.type === 'basic') {
          const responseToCache = response.clone();
          caches.open(CACHE_NAME)
            .then(cache => {
              cache.put(event.request, responseToCache);
            });
        }
        return response;
      })
      .catch(() => {
        // Network failed, try cache
        return caches.match(event.request)
          .then(response => {
            if (response) {
              return response;
            }
            // No cache match, return offline page for navigation requests
            if (event.request.mode === 'navigate' && '${this.config.offlinePageUrl || '/offline.html'}') {
              return caches.match('${this.config.offlinePageUrl || '/offline.html'}');
            }
            // Return empty response for other requests
            return new Response('', { status: 200 });
          });
      })
  );
});`;
  }

  /**
   * Generate cache strategies based on URL patterns
   */
  private generateCacheStrategies(): string {
    const strategies: string[] = [];

    // Network Only (API requests)
    if (this.config.networkOnly && this.config.networkOnly.length > 0) {
      const patterns = this.config.networkOnly.map(pattern => `requestUrl.pathname.includes('${pattern}')`).join(' || ');
      strategies.push(`
  // Network Only - API requests
  if (${patterns}) {
    event.respondWith(
      fetch(event.request).catch(() => {
        return new Response(
          JSON.stringify({ error: 'Network unavailable. Please check your internet connection.' }),
          { headers: { 'Content-Type': 'application/json' } }
        );
      })
    );
    return;
  }`);
    }

    // Cache Only
    if (this.config.cacheOnly && this.config.cacheOnly.length > 0) {
      const patterns = this.config.cacheOnly.map(pattern => `requestUrl.pathname.includes('${pattern}')`).join(' || ');
      strategies.push(`
  // Cache Only
  if (${patterns}) {
    event.respondWith(caches.match(event.request));
    return;
  }`);
    }

    // Cache First
    if (this.config.cacheFirst && this.config.cacheFirst.length > 0) {
      const patterns = this.config.cacheFirst.map(pattern => `requestUrl.pathname.includes('${pattern}')`).join(' || ');
      strategies.push(`
  // Cache First - static assets
  if (${patterns}) {
    event.respondWith(
      caches.match(event.request)
        .then(response => {
          if (response) {
            return response;
          }
          return fetch(event.request).then(fetchResponse => {
            if (fetchResponse && fetchResponse.status === 200) {
              const responseToCache = fetchResponse.clone();
              caches.open(CACHE_NAME).then(cache => {
                cache.put(event.request, responseToCache);
              });
            }
            return fetchResponse;
          });
        })
    );
    return;
  }`);
    }

    // Stale While Revalidate
    if (this.config.staleWhileRevalidate && this.config.staleWhileRevalidate.length > 0) {
      const patterns = this.config.staleWhileRevalidate.map(pattern => `requestUrl.pathname.includes('${pattern}')`).join(' || ');
      strategies.push(`
  // Stale While Revalidate
  if (${patterns}) {
    event.respondWith(
      caches.match(event.request)
        .then(response => {
          const fetchPromise = fetch(event.request).then(fetchResponse => {
            if (fetchResponse && fetchResponse.status === 200) {
              const responseToCache = fetchResponse.clone();
              caches.open(CACHE_NAME).then(cache => {
                cache.put(event.request, responseToCache);
              });
            }
            return fetchResponse;
          });
          
          return response || fetchPromise;
        })
    );
    return;
  }`);
    }

    return strategies.join('\n');
  }

  /**
   * Generate background sync event handler
   */
  private generateSyncEvent(): string {
    if (!this.config.enableBackgroundSync) {
      return '// Background sync is disabled';
    }

    return `// Background Sync
self.addEventListener('sync', event => {
  console.log('Service Worker: Background sync triggered');
  
  if (event.tag === 'background-sync') {
    event.waitUntil(doBackgroundSync());
  }
});

// Background sync implementation
async function doBackgroundSync() {
  try {
    // Get offline data from IndexedDB or localStorage
    const offlineData = await getOfflineData();
    
    if (offlineData && offlineData.length > 0) {
      for (const item of offlineData) {
        try {
          const response = await fetch('/api/sync', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(item)
          });

          if (response.ok) {
            await removeOfflineData(item.id);
          }
        } catch (error) {
          console.error('Sync failed for item:', item.id, error);
        }
      }
    }
  } catch (error) {
    console.error('Background sync failed:', error);
  }
}`;
  }

  /**
   * Generate push notification event handlers
   */
  private generatePushNotificationEvents(): string {
    if (!this.config.enablePushNotifications) {
      return '// Push notifications are disabled';
    }

    return `// Push Notifications
self.addEventListener('push', event => {
  console.log('Service Worker: Push notification received');
  
  const options = {
    body: event.data ? event.data.text() : 'New notification available',
    icon: '/assets/icons/icon-192x192.png',
    badge: '/assets/icons/icon-72x72.png',
    vibrate: [200, 100, 200],
    tag: 'pwa-notification',
    renotify: true,
    requireInteraction: false,
    actions: [
      {
        action: 'open',
        title: 'Open',
        icon: '/assets/icons/icon-72x72.png'
      },
      {
        action: 'close',
        title: 'Close',
        icon: '/assets/icons/icon-72x72.png'
      }
    ]
  };

  event.waitUntil(
    self.registration.showNotification('${this.config.cacheName}', options)
  );
});

// Notification click handler
self.addEventListener('notificationclick', event => {
  console.log('Service Worker: Notification clicked');
  event.notification.close();

  if (event.action === 'open' || !event.action) {
    event.waitUntil(
      clients.openWindow('/')
    );
  }
});`;
  }

  /**
   * Generate update handling
   */
  private generateUpdateHandling(): string {
    return `// Update Handling
self.addEventListener('message', event => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
    event.ports[0].postMessage({ type: 'UPDATE_APPLIED' });
  }
  
  if (event.data && event.data.type === 'CHECK_FOR_UPDATE') {
    checkForUpdate().then(updateAvailable => {
      event.ports[0].postMessage({
        type: 'UPDATE_CHECK_RESULT',
        updateAvailable: updateAvailable,
        currentVersion: CACHE_VERSION
      });
    });
  }
});

// Check for updates
async function checkForUpdate() {
  try {
    const response = await fetch('/service-worker.js', { cache: 'no-cache' });
    const newSWContent = await response.text();
    
    const versionMatch = newSWContent.match(/const CACHE_VERSION = '([^']+)'/);
    const newVersion = versionMatch ? versionMatch[1] : null;
    
    if (newVersion && newVersion !== CACHE_VERSION) {
      console.log(\`New version detected! Current: \${CACHE_VERSION}, New: \${newVersion}\`);
      return true;
    }
    
    return false;
  } catch (error) {
    console.error('Update check failed:', error);
    return false;
  }
}`;
  }

  /**
   * Generate utility functions
   */
  private generateUtilityFunctions(): string {
    return `// Utility Functions
async function getOfflineData() {
  // Implement based on your storage needs (IndexedDB, localStorage, etc.)
  try {
    // Example implementation for localStorage
    const data = localStorage.getItem('offline-data');
    return data ? JSON.parse(data) : [];
  } catch (error) {
    console.error('Failed to get offline data:', error);
    return [];
  }
}

async function removeOfflineData(id) {
  // Implement based on your storage needs
  try {
    const data = await getOfflineData();
    const filtered = data.filter(item => item.id !== id);
    localStorage.setItem('offline-data', JSON.stringify(filtered));
  } catch (error) {
    console.error('Failed to remove offline data:', error);
  }
}

// Network status detection
function isOnline() {
  return navigator.onLine;
}

// Cache size management
async function getCacheSize() {
  const cacheNames = await caches.keys();
  let totalSize = 0;
  
  for (const cacheName of cacheNames) {
    const cache = await caches.open(cacheName);
    const requests = await cache.keys();
    
    for (const request of requests) {
      const response = await cache.match(request);
      if (response) {
        const blob = await response.blob();
        totalSize += blob.size;
      }
    }
  }
  
  return totalSize;
}

// Cleanup old caches if size exceeds limit
async function cleanupIfNeeded(maxSizeBytes = 50 * 1024 * 1024) { // 50MB default
  const currentSize = await getCacheSize();
  
  if (currentSize > maxSizeBytes) {
    const cacheNames = await caches.keys();
    const oldCaches = cacheNames.filter(name => name !== CACHE_NAME);
    
    // Delete oldest caches first
    for (const cacheName of oldCaches) {
      await caches.delete(cacheName);
      console.log('Cleaned up old cache:', cacheName);
      
      const newSize = await getCacheSize();
      if (newSize <= maxSizeBytes) {
        break;
      }
    }
  }
}`;
  }

  /**
   * Validate configuration
   */
  private validateConfig(): void {
    const required = ['cacheName', 'version', 'urlsToCache'];
    
    for (const field of required) {
      if (!this.config[field as keyof ServiceWorkerConfig]) {
        throw new ServiceWorkerError(`Missing required field: ${field}`);
      }
    }

    if (!Array.isArray(this.config.urlsToCache) || this.config.urlsToCache.length === 0) {
      throw new ServiceWorkerError('urlsToCache must be a non-empty array');
    }
  }

  /**
   * Update configuration
   */
  updateConfig(newConfig: Partial<ServiceWorkerConfig>): void {
    this.config = { ...this.config, ...newConfig };
    this.validateConfig();
  }

  /**
   * Get current configuration
   */
  getConfig(): ServiceWorkerConfig {
    return { ...this.config };
  }

  /**
   * Create service worker templates for common use cases
   */
  static createTemplates() {
    return {
      /**
       * Basic service worker with essential features
       */
      basic: (config: Partial<ServiceWorkerConfig>): ServiceWorkerGenerator => {
        const basicConfig: ServiceWorkerConfig = {
          cacheName: 'basic-pwa',
          version: '1.0.0',
          urlsToCache: [
            '/',
            '/assets/css/style.css',
            '/assets/js/main.js',
            '/offline.html'
          ],
          offlinePageUrl: '/offline.html',
          skipWaiting: true,
          clientsClaim: true,
          ...config
        };

        return new ServiceWorkerGenerator(basicConfig);
      },

      /**
       * Advanced service worker with all features
       */
      advanced: (config: Partial<ServiceWorkerConfig>): ServiceWorkerGenerator => {
        const advancedConfig: ServiceWorkerConfig = {
          cacheName: 'advanced-pwa',
          version: '1.0.0',
          urlsToCache: [
            '/',
            '/assets/css/style.css',
            '/assets/js/main.js',
            '/offline.html'
          ],
          networkFirst: ['/api/'],
          cacheFirst: ['/assets/', '/images/'],
          staleWhileRevalidate: ['/data/'],
          networkOnly: ['/api/real-time'],
          offlinePageUrl: '/offline.html',
          enableBackgroundSync: true,
          enablePushNotifications: true,
          skipWaiting: true,
          clientsClaim: true,
          ...config
        };

        return new ServiceWorkerGenerator(advancedConfig);
      },

      /**
       * Offline-first service worker
       */
      offlineFirst: (config: Partial<ServiceWorkerConfig>): ServiceWorkerGenerator => {
        const offlineConfig: ServiceWorkerConfig = {
          cacheName: 'offline-first-pwa',
          version: '1.0.0',
          urlsToCache: [
            '/',
            '/assets/css/style.css',
            '/assets/js/main.js',
            '/offline.html'
          ],
          cacheFirst: ['/', '/assets/', '/data/'],
          networkOnly: ['/api/sync'],
          offlinePageUrl: '/offline.html',
          enableBackgroundSync: true,
          skipWaiting: true,
          clientsClaim: true,
          ...config
        };

        return new ServiceWorkerGenerator(offlineConfig);
      }
    };
  }
}