/**
 * API Request/Response Logger for Maximum Developer Transparency
 * Captures all API requests, responses, and timing information
 */

import { getGlobalErrorHandler } from './GlobalErrorHandler';
import { ConsoleLogger } from './logging/ConsoleLogger';

export interface ApiRequestLog {
  id: string;
  timestamp: string;
  url: string;
  method: string;
  headers?: Record<string, string>;
  body?: unknown;
  response?: {
    status: number;
    statusText: string;
    headers: Headers;
    body: unknown;
    duration: number;
  };
  error?: {
    message: string;
    type: string;
    stack?: string;
  };
  metadata: {
    timestamp: string;
    duration: number;
    retryCount: number;
    wasRetry: boolean;
  };
}

export interface ApiRequestLoggerOptions {
  /**
   * Log all requests to console
   */
  logToConsole?: boolean;
  
  /**
   * Log request bodies
   */
  logBodies?: boolean;
  
  /**
   * Log response bodies
   */
  logResponses?: boolean;
  
  /**
   * Maximum number of requests to keep in history
   */
  maxHistory?: number;
  
  /**
   * Filter out certain requests (e.g., health checks)
   */
  requestFilter?: (url: string, method: string) => boolean;
  
  /**
   * Mask sensitive data in logs
   */
  maskSensitiveData?: boolean;
  
  /**
   * Sensitive keys to mask
   */
  sensitiveKeys?: string[];
}

export class ApiRequestLogger {
  private options: ApiRequestLoggerOptions;
  private logger: ConsoleLogger;
  private requestHistory: ApiRequestLog[] = [];
  private requestCounter = 0;
  private activeRequests = new Map<string, ApiRequestLog>();

  constructor(options: ApiRequestLoggerOptions = {}) {
    this.options = {
      logToConsole: options.logToConsole ?? process.env.NODE_ENV === 'development',
      logBodies: options.logBodies ?? process.env.NODE_ENV === 'development',
      logResponses: options.logResponses ?? process.env.NODE_ENV === 'development',
      maxHistory: options.maxHistory ?? 100,
      requestFilter: options.requestFilter,
      maskSensitiveData: options.maskSensitiveData ?? true,
      sensitiveKeys: options.sensitiveKeys ?? ['password', 'token', 'authorization', 'cookie', 'secret', 'key'],
      ...options,
    };

    this.logger = new ConsoleLogger();
  }

  /**
   * Generate unique request ID
   */
  private generateId(): string {
    this.requestCounter++;
    return `req_${Date.now()}_${this.requestCounter}`;
  }

  /**
   * Check if request should be logged
   */
  private shouldLog(url: string, method: string): boolean {
    if (this.options.requestFilter) {
      return this.options.requestFilter(url, method);
    }
    
    // Filter out health checks and metrics by default
    const filteredPatterns = ['/health', '/metrics', '/api/health', '/api/metrics'];
    return !filteredPatterns.some(pattern => url.includes(pattern));
  }

  /**
   * Mask sensitive data in objects
   */
  private maskSensitiveData(data: unknown): unknown {
    if (!this.options.maskSensitiveData) return data;
    
    if (typeof data === 'string') {
      // Check if string contains sensitive data
      const lower = data.toLowerCase();
      if (this.options.sensitiveKeys!.some(key => lower.includes(key))) {
        return '[MASKED]';
      }
      return data;
    }

    if (Array.isArray(data)) {
      return data.map(item => this.maskSensitiveData(item));
    }

    if (typeof data === 'object' && data !== null) {
      const masked: Record<string, unknown> = {};
      for (const [key, value] of Object.entries(data)) {
        const lowerKey = key.toLowerCase();
        if (this.options.sensitiveKeys!.some(sensitive => lowerKey.includes(sensitive))) {
          masked[key] = '[MASKED]';
        } else {
          masked[key] = this.maskSensitiveData(value);
        }
      }
      return masked;
    }

    return data;
  }

  /**
   * Log request start
   */
  logRequest(url: string, method: string, headers?: Record<string, string>, body?: unknown): string {
    if (!this.shouldLog(url, method)) {
      return '';
    }

    const id = this.generateId();
    const timestamp = new Date().toISOString();

    const log: ApiRequestLog = {
      id,
      timestamp,
      url,
      method,
      headers: this.maskSensitiveData(headers) as Record<string, string> | undefined,
      body: this.maskSensitiveData(body),
      metadata: {
        timestamp,
        duration: 0,
        retryCount: 0,
        wasRetry: false,
      },
    };

    this.activeRequests.set(id, log);

    if (this.options.logToConsole) {
      // Use enhanced logger for beautiful output
      this.logger.debug(`API Request: ${method} ${url}`, {
        requestId: id,
        timestamp,
        headers: this.options.logBodies ? log.headers : '[headers hidden]',
        body: this.options.logBodies ? log.body : '[body hidden]',
      });
    }

    return id;
  }

  /**
   * Log successful response
   */
  logResponse(id: string, response: Response, body: unknown, duration: number): void {
    const log = this.activeRequests.get(id);
    if (!log) return;

    log.response = {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
      body: this.maskSensitiveData(body),
      duration,
    };
    log.metadata.duration = duration;

    // Move from active to history
    this.activeRequests.delete(id);
    this.addToHistory(log);

    if (this.options.logToConsole) {
      const isSuccess = response.ok;
      const context = {
        requestId: id,
        duration: `${duration.toFixed(2)}ms`,
        status: `${response.status} ${response.statusText}`,
        ...(this.options.logResponses && { body: log.response.body }),
      };

      if (isSuccess) {
        this.logger.info(`API Response: ${log.method} ${log.url}`, context);
      } else {
        this.logger.warn(`API Response: ${log.method} ${log.url}`, context);
      }
    }
  }

  /**
   * Log error response
   */
  logError(id: string, error: Error, duration: number): void {
    const log = this.activeRequests.get(id);
    if (!log) return;

    log.error = {
      message: error.message,
      type: error.name,
      stack: error.stack,
    };
    log.metadata.duration = duration;

    // Move from active to history
    this.activeRequests.delete(id);
    this.addToHistory(log);

    if (this.options.logToConsole) {
      const isNetworkError = error.message.includes('fetch') ||
                            error.message.includes('Failed to fetch') ||
                            error.message.includes('NetworkError');

      const context = {
        requestId: id,
        duration: `${duration.toFixed(2)}ms`,
        errorType: error.name,
        ...(process.env.NODE_ENV === 'development' && error.stack ? { stack: error.stack } : {}),
      };

      // Use warn level for network errors (expected), error level for others
      if (isNetworkError) {
        this.logger.warn(`API Network Error: ${log.method} ${log.url}`, context);
      } else {
        this.logger.error(`API Error: ${log.method} ${log.url}`, error, context);
      }
    }

    // Don't report network errors to external services (they're expected)
    const isNetworkError = error.message.includes('fetch') ||
                          error.message.includes('Failed to fetch') ||
                          error.message.includes('NetworkError');

    if (!isNetworkError) {
      const globalHandler = getGlobalErrorHandler();
      globalHandler.report(error, {
        source: 'api_request',
        url: log.url,
        method: log.method,
        duration,
        requestId: id,
      });
    }
  }

  /**
   * Update retry count for a request
   */
  updateRetryCount(id: string, retryCount: number): void {
    const log = this.activeRequests.get(id);
    if (log) {
      log.metadata.retryCount = retryCount;
      log.metadata.wasRetry = retryCount > 0;
    }
  }

  /**
   * Add log to history
   */
  private addToHistory(log: ApiRequestLog): void {
    this.requestHistory.push(log);

    // Keep only last N requests
    const maxHistory = this.options.maxHistory || 100;
    if (this.requestHistory.length > maxHistory) {
      this.requestHistory = this.requestHistory.slice(-maxHistory);
    }
  }

  /**
   * Get request history
   */
  getHistory(): ApiRequestLog[] {
    return [...this.requestHistory];
  }

  /**
   * Get active requests
   */
  getActiveRequests(): ApiRequestLog[] {
    return Array.from(this.activeRequests.values());
  }

  /**
   * Clear history
   */
  clearHistory(): void {
    this.requestHistory = [];
    if (this.options.logToConsole) {
      this.logger.info('API request history cleared');
    }
  }

  /**
   * Get statistics
   */
  getStats(): {
    total: number;
    successful: number;
    failed: number;
    averageDuration: number;
    byMethod: Record<string, number>;
    byStatus: Record<number, number>;
  } {
    const stats = {
      total: this.requestHistory.length,
      successful: 0,
      failed: 0,
      averageDuration: 0,
      byMethod: {} as Record<string, number>,
      byStatus: {} as Record<number, number>,
    };

    let totalDuration = 0;

    this.requestHistory.forEach(log => {
      if (log.response) {
        if (log.response.status >= 200 && log.response.status < 300) {
          stats.successful++;
        } else {
          stats.failed++;
        }
        totalDuration += log.response.duration;

        stats.byStatus[log.response.status] = (stats.byStatus[log.response.status] || 0) + 1;
      } else if (log.error) {
        stats.failed++;
      }

      stats.byMethod[log.method] = (stats.byMethod[log.method] || 0) + 1;
    });

    stats.averageDuration = stats.total > 0 ? totalDuration / stats.total : 0;

    return stats;
  }

  /**
   * Export logs for debugging
   */
  exportLogs(format: 'json' | 'text' = 'json'): string {
    if (format === 'json') {
      return JSON.stringify(this.requestHistory, null, 2);
    }

    // Text format
    return this.requestHistory.map(log => {
      const parts = [
        `[${log.timestamp}] ${log.method} ${log.url}`,
        `ID: ${log.id}`,
      ];

      if (log.response) {
        parts.push(`Status: ${log.response.status}`);
        parts.push(`Duration: ${log.response.duration.toFixed(2)}ms`);
      }

      if (log.error) {
        parts.push(`Error: ${log.error.message}`);
      }

      if (log.body && this.options.logBodies) {
        parts.push(`Body: ${JSON.stringify(log.body)}`);
      }

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

  /**
   * Find requests by URL pattern
   */
  findRequests(urlPattern: string | RegExp): ApiRequestLog[] {
    const pattern = typeof urlPattern === 'string' 
      ? new RegExp(urlPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
      : urlPattern;

    return this.requestHistory.filter(log => pattern.test(log.url));
  }

  /**
   * Get slowest requests
   */
  getSlowestRequests(limit: number = 10): ApiRequestLog[] {
    return this.requestHistory
      .filter(log => log.response && log.response.duration > 0)
      .sort((a, b) => (b.response?.duration || 0) - (a.response?.duration || 0))
      .slice(0, limit);
  }

  /**
   * Get requests by time range
   */
  getRequestsByTimeRange(startTime: Date, endTime: Date): ApiRequestLog[] {
    return this.requestHistory.filter(log => {
      const logTime = new Date(log.timestamp);
      return logTime >= startTime && logTime <= endTime;
    });
  }

  /**
   * Update options dynamically
   */
  updateOptions(newOptions: Partial<ApiRequestLoggerOptions>): void {
    this.options = { ...this.options, ...newOptions };
  }

  /**
   * Create a logged fetch function
   */
  createLoggedFetch(): typeof fetch {
    const logger = this;
    const originalFetch = typeof window !== 'undefined' ? window.fetch : fetch;

    return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
      const startTime = performance.now();
      const url = typeof input === 'string' ? input : input.toString();
      const method = init?.method || 'GET';

      // Log request start
      const requestId = logger.logRequest(
        url,
        method,
        init?.headers as Record<string, string> | undefined,
        init?.body ? JSON.parse(init.body as string) : undefined
      );

      try {
        const response = await originalFetch(input, init);
        const endTime = performance.now();
        const duration = endTime - startTime;

        // Clone response to read body without consuming it
        const responseClone = response.clone();
        
        let responseBody: unknown;
        try {
          const contentType = responseClone.headers.get('content-type');
          if (contentType && contentType.includes('application/json')) {
            responseBody = await responseClone.json();
          } else {
            responseBody = await responseClone.text();
          }
        } catch {
          responseBody = '[Unable to read response body]';
        }

        // Log successful response
        logger.logResponse(requestId, response, responseBody, duration);

        return response;
      } catch (error) {
        const endTime = performance.now();
        const duration = endTime - startTime;

        // Log error
        if (error instanceof Error) {
          logger.logError(requestId, error, duration);
        }

        throw error;
      }
    };
  }
}

/**
 * Global instance accessor
 */
let globalApiLoggerInstance: ApiRequestLogger | null = null;

export function getGlobalApiLogger(): ApiRequestLogger {
  if (!globalApiLoggerInstance) {
    globalApiLoggerInstance = new ApiRequestLogger();
  }
  return globalApiLoggerInstance;
}

/**
 * Initialize global API logger
 */
export function initializeApiLogger(options?: ApiRequestLoggerOptions): ApiRequestLogger {
  const logger = new ApiRequestLogger(options);
  globalApiLoggerInstance = logger;
  return logger;
}

/**
 * Fetch interceptor that automatically logs all requests
 */
export function createLoggedFetch(originalFetch: typeof fetch = typeof window !== 'undefined' ? window.fetch : fetch): typeof fetch {
  const logger = getGlobalApiLogger();

  return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
    const startTime = performance.now();
    const url = typeof input === 'string' ? input : input.toString();
    const method = init?.method || 'GET';

    // Log request start
    const requestId = logger.logRequest(
      url,
      method,
      init?.headers as Record<string, string> | undefined,
      init?.body ? JSON.parse(init.body as string) : undefined
    );

    try {
      const response = await originalFetch(input, init);
      const endTime = performance.now();
      const duration = endTime - startTime;

      // Clone response to read body without consuming it
      const responseClone = response.clone();
      
      let responseBody: unknown;
      try {
        const contentType = responseClone.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
          responseBody = await responseClone.json();
        } else {
          responseBody = await responseClone.text();
        }
      } catch {
        responseBody = '[Unable to read response body]';
      }

      // Log successful response
      logger.logResponse(requestId, response, responseBody, duration);

      return response;
    } catch (error) {
      const endTime = performance.now();
      const duration = endTime - startTime;

      // Log error
      if (error instanceof Error) {
        logger.logError(requestId, error, duration);
      }

      throw error;
    }
  };
}

/**
 * React hook for API logging
 */
export function useApiLogger() {
  const logger = getGlobalApiLogger();

  return {
    getHistory: () => logger.getHistory(),
    getStats: () => logger.getStats(),
    clearHistory: () => logger.clearHistory(),
    exportLogs: (format?: 'json' | 'text') => logger.exportLogs(format),
    findRequests: (pattern: string | RegExp) => logger.findRequests(pattern),
    getSlowestRequests: (limit?: number) => logger.getSlowestRequests(limit),
    getRequestsByTimeRange: (start: Date, end: Date) => logger.getRequestsByTimeRange(start, end),
    updateOptions: (options: Partial<ApiRequestLoggerOptions>) => logger.updateOptions(options),
  };
}