/**
 * Client-side Push Notification Manager
 * 
 * Handles push notification subscription/unsubscription
 * and permission management in the browser.
 * 
 * SSR-Safe: All browser API access is guarded and lazily initialized
 */

import { browser } from '$app/environment';

interface NotificationState {
  supported: boolean;
  permission: NotificationPermission;
  subscribed: boolean;
  loading: boolean;
  error: string | null;
}

class PushNotificationManager {
  private state: NotificationState = {
    supported: false,
    permission: 'default',
    subscribed: false,
    loading: false,
    error: null
  };

  private listeners: Array<(state: NotificationState) => void> = [];
  private registration: ServiceWorkerRegistration | null = null;
  private _clientId: string | null = null;
  private _initialized = false;

  constructor() {
    // SSR-safe constructor: no browser API access
    // Initialization happens lazily when needed
  }

  /**
   * Lazy initialization - only runs in browser context
   */
  private ensureInitialized(): void {
    if (this._initialized || !browser) return;
    
    this._initialized = true;
    this.checkSupport();
    this.initializeServiceWorker();
  }

  /**
   * Get clientId lazily - only generates in browser context
   */
  private get clientId(): string {
    if (!this._clientId && browser) {
      this._clientId = this.generateClientId();
    }
    return this._clientId || 'ssr-fallback';
  }

  /**
   * Subscribe to state changes
   */
  onStateChange(callback: (state: NotificationState) => void): () => void {
    this.ensureInitialized(); // Ensure initialized before sending state
    
    this.listeners.push(callback);
    callback(this.state); // Send initial state
    
    return () => {
      this.listeners = this.listeners.filter(cb => cb !== callback);
    };
  }

  /**
   * Get current state
   */
  getState(): NotificationState {
    this.ensureInitialized();
    return { ...this.state };
  }

  /**
   * Check if push notifications are supported
   * SSR-safe: guarded with browser check
   */
  private checkSupport(): void {
    if (!browser) {
      this.state.supported = false;
      this.state.permission = 'denied';
      return;
    }
    
    this.state.supported = (
      'serviceWorker' in navigator &&
      'PushManager' in window &&
      'Notification' in window
    );
    
    this.state.permission = this.state.supported ? Notification.permission : 'denied';
  }

  /**
   * Initialize service worker registration
   * SSR-safe: guarded with browser and support checks
   */
  private async initializeServiceWorker(): Promise<void> {
    if (!browser || !this.state.supported) return;

    try {
      // Wait for service worker to be ready
      this.registration = await navigator.serviceWorker.ready;
      console.log('[PushManager] Service worker ready');
      
      // Check if already subscribed
      const subscription = await this.registration.pushManager.getSubscription();
      this.state.subscribed = !!subscription;
      
      this.notifyListeners();
    } catch (error) {
      console.error('[PushManager] Service worker initialization failed:', error);
      this.state.error = 'Service worker not available';
      this.notifyListeners();
    }
  }

  /**
   * Request notification permission
   */
  async requestPermission(): Promise<boolean> {
    this.ensureInitialized();
    
    if (!browser || !this.state.supported) {
      this.state.error = 'Push notifications not supported';
      this.notifyListeners();
      return false;
    }

    if (this.state.permission === 'granted') {
      return true;
    }

    try {
      this.state.loading = true;
      this.notifyListeners();

      const permission = await Notification.requestPermission();
      this.state.permission = permission;
      this.state.error = permission === 'denied' ? 'Permission denied' : null;
      
      this.state.loading = false;
      this.notifyListeners();
      
      return permission === 'granted';
    } catch (error) {
      console.error('[PushManager] Permission request failed:', error);
      this.state.error = 'Failed to request permission';
      this.state.loading = false;
      this.notifyListeners();
      return false;
    }
  }

  /**
   * Subscribe to push notifications
   */
  async subscribe(): Promise<boolean> {
    if (!await this.requestPermission()) {
      return false;
    }

    if (!this.registration) {
      this.state.error = 'Service worker not ready';
      this.notifyListeners();
      return false;
    }

    try {
      this.state.loading = true;
      this.state.error = null;
      this.notifyListeners();

      // Get VAPID public key from server
      const vapidResponse = await fetch('/api/notifications/vapid-key');
      if (!vapidResponse.ok) {
        throw new Error('Failed to get VAPID key');
      }
      
      const { publicKey } = await vapidResponse.json();
      
      // Create push subscription
      const subscription = await this.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: this.urlBase64ToUint8Array(publicKey)
      });

      // Send subscription to server
      const subscribeResponse = await fetch('/api/notifications/subscribe', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          subscription: subscription.toJSON(),
          clientId: this.clientId
        })
      });

      if (!subscribeResponse.ok) {
        throw new Error('Failed to register subscription with server');
      }

      this.state.subscribed = true;
      this.state.loading = false;
      this.notifyListeners();
      
      console.log('[PushManager] Successfully subscribed to push notifications');
      return true;

    } catch (error) {
      console.error('[PushManager] Subscription failed:', error);
      this.state.error = 'Failed to subscribe to notifications';
      this.state.loading = false;
      this.notifyListeners();
      return false;
    }
  }

  /**
   * Unsubscribe from push notifications
   */
  async unsubscribe(): Promise<boolean> {
    if (!this.registration) {
      this.state.error = 'Service worker not ready';
      this.notifyListeners();
      return false;
    }

    try {
      this.state.loading = true;
      this.state.error = null;
      this.notifyListeners();

      // Get current subscription
      const subscription = await this.registration.pushManager.getSubscription();
      
      if (subscription) {
        // Unsubscribe from push service
        await subscription.unsubscribe();
        
        // Remove from server
        await fetch('/api/notifications/subscribe', {
          method: 'DELETE',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            clientId: this.clientId
          })
        });
      }

      this.state.subscribed = false;
      this.state.loading = false;
      this.notifyListeners();
      
      console.log('[PushManager] Successfully unsubscribed from push notifications');
      return true;

    } catch (error) {
      console.error('[PushManager] Unsubscription failed:', error);
      this.state.error = 'Failed to unsubscribe from notifications';
      this.state.loading = false;
      this.notifyListeners();
      return false;
    }
  }

  /**
   * Toggle subscription state
   */
  async toggleSubscription(): Promise<boolean> {
    if (this.state.subscribed) {
      return await this.unsubscribe();
    } else {
      return await this.subscribe();
    }
  }

  /**
   * Generate unique client ID
   * SSR-safe: guarded with browser check, uses localStorage only in browser
   */
  private generateClientId(): string {
    if (!browser) return '';
    
    const stored = localStorage.getItem('push-client-id');
    if (stored) return stored;

    const id = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    localStorage.setItem('push-client-id', id);
    return id;
  }

  /**
   * Convert URL-safe base64 string to Uint8Array
   * Enhanced with validation and error handling for VAPID keys
   * SSR-safe: uses window.atob only in browser context
   */
  private urlBase64ToUint8Array(base64String: string): Uint8Array {
    if (!browser) {
      return new Uint8Array(0);
    }

    // Input validation
    if (!base64String || typeof base64String !== 'string') {
      console.error('[PushManager] Invalid VAPID key: empty or non-string');
      return new Uint8Array(0);
    }

    // Remove whitespace and validate format
    const cleanKey = base64String.trim();
    if (cleanKey.length === 0) {
      console.error('[PushManager] Invalid VAPID key: empty string');
      return new Uint8Array(0);
    }

    // VAPID keys should be 65 characters (unpadded base64)
    if (cleanKey.length !== 65) {
      console.warn(`[PushManager] VAPID key length ${cleanKey.length}, expected 65`);
    }

    try {
      // Add proper padding
      const padding = '='.repeat((4 - cleanKey.length % 4) % 4);
      const base64 = (cleanKey + padding)
        .replace(/-/g, '+')
        .replace(/_/g, '/');

      // Validate base64 format before decoding
      const base64Regex = /^[A-Za-z0-9+\/]*={0,2}$/;
      if (!base64Regex.test(base64)) {
        throw new Error('Invalid base64 characters');
      }

      const rawData = window.atob(base64);
      const outputArray = new Uint8Array(rawData.length);

      for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
      }

      console.log(`[PushManager] Successfully decoded VAPID key (${outputArray.length} bytes)`);
      return outputArray;

    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      console.error('[PushManager] Failed to decode VAPID key:', error, 'Key:', cleanKey);
      throw new Error(`Invalid VAPID key format: ${errorMessage}`);
    }
  }

  /**
   * Notify all listeners of state change
   */
  private notifyListeners(): void {
    this.listeners.forEach(callback => {
      try {
        callback({ ...this.state });
      } catch (error) {
        console.error('[PushManager] Listener error:', error);
      }
    });
  }
}

// Singleton instance
export const pushNotificationManager = new PushNotificationManager();

export type { NotificationState };