/**
 * Graceful degradation utilities for when API is unavailable
 */

import { ApiConnectionMonitor } from './ApiConnectionMonitor';
import { ApiError } from './ApiError';

export interface DegradationOptions<T> {
  /**
   * Fallback data to return when API is unavailable
   */
  fallback?: T;
  
  /**
   * Whether to throw error or return fallback
   */
  throwOnError?: boolean;
  
  /**
   * Maximum time to wait for API response
   */
  timeout?: number;
  
  /**
   * Whether to use cached data if available
   */
  useCache?: boolean;
}

export interface CacheEntry<T> {
  data: T;
  timestamp: Date;
  expiry: Date;
}

/**
 * Simple in-memory cache for API responses
 */
class ResponseCache {
  private cache = new Map<string, CacheEntry<unknown>>();

  /**
   * Get cached data if not expired
   */
  get<T>(key: string): T | null {
    const entry = this.cache.get(key);
    if (!entry) return null;

    if (new globalThis.Date() > entry.expiry) {
      this.cache.delete(key);
      return null;
    }

    return entry.data as T;
  }

  /**
   * Set cached data with expiry
   */
  set<T>(key: string, data: T, ttlMs: number = 300000): void {
    const now = new globalThis.Date();
    const expiry = new globalThis.Date(now.getTime() + ttlMs);
    
    this.cache.set(key, {
      data,
      timestamp: now,
      expiry,
    });
  }

  /**
   * Clear all cached data
   */
  clear(): void {
    this.cache.clear();
  }

  /**
   * Get cache statistics
   */
  getStats() {
    return {
      size: this.cache.size,
      entries: Array.from(this.cache.entries()).map(([key, entry]) => ({
        key,
        timestamp: entry.timestamp,
        expiry: entry.expiry,
      })),
    };
  }
}

/**
 * Global cache instance
 */
export const responseCache = new ResponseCache();

/**
 * Execute a function with graceful degradation
 */
export async function withGracefulDegradation<T>(
  fn: () => Promise<T>,
  options: DegradationOptions<T> = {}
): Promise<T | undefined> {
  const {
    fallback,
    throwOnError = false,
    timeout = 10000,
    useCache = true,
  } = options;

  const monitor = ApiConnectionMonitor.getInstance();

  // Check if API is available
  if (!monitor.isAvailable()) {
    // Try cache first
    if (useCache && options.fallback) {
      const cacheKey = `graceful:${fn.toString()}`;
      const cached = responseCache.get<T>(cacheKey);
      if (cached) {
        return cached;
      }
    }

    // Return fallback
    if (fallback !== undefined) {
      return fallback;
    }

    // Throw error if no fallback
    if (throwOnError) {
      throw new ApiError(
        'API unavailable and no fallback provided',
        'NETWORK_ERROR',
        {
          timestamp: new globalThis.Date().toISOString(),
        }
      );
    }

    // Return undefined (caller must handle)
    return undefined;
  }

  // API is available, try to execute
  try {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    const result = await Promise.race([
      fn(),
      new Promise<never>((_, reject) => {
        controller.signal.addEventListener('abort', () => {
          reject(new Error('Request timeout'));
        });
      }),
    ]);

    clearTimeout(timeoutId);

    // Cache the result if enabled
    if (useCache && result !== null && result !== undefined) {
      const cacheKey = `graceful:${fn.toString()}`;
      responseCache.set(cacheKey, result);
    }

    return result;

  } catch (error) {
    // Record failure in monitor
    if (error instanceof ApiError) {
      monitor.recordFailure(error);
    } else {
      monitor.recordFailure(error as Error);
    }

    // Try cache as fallback
    if (useCache && options.fallback) {
      const cacheKey = `graceful:${fn.toString()}`;
      const cached = responseCache.get<T>(cacheKey);
      if (cached) {
        return cached;
      }
    }

    // Return fallback if provided
    if (fallback !== undefined) {
      return fallback;
    }

    // Re-throw or return undefined
    if (throwOnError) {
      throw error;
    }

    return undefined;
  }
}

/**
 * Service wrapper for graceful degradation
 */
export class GracefulService<T> {
  private monitor: ApiConnectionMonitor;
  private cacheKey: string;

  constructor(
    private serviceName: string,
    private getData: () => Promise<T>,
    private defaultFallback: T
  ) {
    this.monitor = ApiConnectionMonitor.getInstance();
    this.cacheKey = `service:${serviceName}`;
  }

  /**
   * Get data with graceful degradation
   */
  async get(options: Partial<DegradationOptions<T>> = {}): Promise<T> {
    const result = await withGracefulDegradation(this.getData, {
      fallback: this.defaultFallback,
      throwOnError: false,
      useCache: true,
      ...options,
    });

    return result ?? this.defaultFallback;
  }

  /**
   * Force refresh data
   */
  async refresh(): Promise<T> {
    responseCache.clear(); // Clear cache for this service
    return this.get({ useCache: false });
  }

  /**
   * Get service health status
   */
  getStatus() {
    const health = this.monitor.getHealth();
    const isAvailable = this.monitor.isAvailable();
    
    return {
      serviceName: this.serviceName,
      available: isAvailable,
      reliability: health.totalRequests > 0 
        ? (health.successfulRequests / health.totalRequests) * 100 
        : 100,
      lastCheck: health.lastCheck,
    };
  }
}

/**
 * Offline mode detection
 */
export class OfflineDetector {
  private static instance: OfflineDetector;
  private isOffline = false;
  private listeners: Array<(isOffline: boolean) => void> = [];

  private constructor() {
    if (typeof window !== 'undefined') {
      window.addEventListener('online', () => this.setOffline(false));
      window.addEventListener('offline', () => this.setOffline(true));
      
      // Initial check
      this.isOffline = !navigator.onLine;
    }
  }

  static getInstance(): OfflineDetector {
    if (!OfflineDetector.instance) {
      OfflineDetector.instance = new OfflineDetector();
    }
    return OfflineDetector.instance;
  }

  private setOffline(offline: boolean): void {
    if (this.isOffline !== offline) {
      this.isOffline = offline;
      this.listeners.forEach(listener => listener(offline));
    }
  }

  /**
   * Check if browser is offline
   */
  isBrowserOffline(): boolean {
    return this.isOffline;
  }

  /**
   * Add listener for offline status changes
   */
  onStatusChange(callback: (isOffline: boolean) => void): void {
    this.listeners.push(callback);
  }

  /**
   * Remove listener
   */
  removeListener(callback: (isOffline: boolean) => void): void {
    this.listeners = this.listeners.filter(cb => cb !== callback);
  }
}

/**
 * Hook for offline detection
 */
export function useOfflineStatus() {
  if (typeof window === 'undefined') {
    return false; // Server-side
  }

  // This would need to be used in a React component context
  // For now, provide a simple check function
  return OfflineDetector.getInstance().isBrowserOffline();
}