/**
 * Header Redaction Service for Redirect Intelligence v2
 * 
 * Sanitizes and redacts sensitive headers for security and privacy
 */

import { logger } from '../lib/logger';

/**
 * Headers that should be completely removed from storage/logs
 */
const SENSITIVE_HEADERS = new Set([
  // Authentication headers
  'authorization',
  'x-api-key',
  'x-auth-token',
  'x-access-token',
  'bearer',
  'token',
  
  // Session headers
  'cookie',
  'set-cookie',
  'session',
  'session-id',
  'sessionid',
  'jsessionid',
  'phpsessid',
  
  // Personal information
  'x-real-ip',
  'x-forwarded-for',
  'x-client-ip',
  'x-remote-addr',
  'x-user-email',
  'x-user-id',
  'x-username',
  
  // Security tokens
  'x-csrf-token',
  'x-xsrf-token',
  'x-csrftoken',
  'csrf-token',
  'xsrf-token',
  
  // Custom application headers that might contain sensitive data
  'x-api-secret',
  'x-private-key',
  'x-secret',
  'x-password',
  'x-token',
  'x-auth',
  'x-authentication',
  
  // Server internal headers
  'x-forwarded-proto',
  'x-forwarded-host',
  'x-forwarded-server',
  'x-original-forwarded-for',
  'x-cluster-client-ip',
  'cf-connecting-ip',
  'true-client-ip',
  
  // Application-specific sensitive headers
  'stripe-signature',
  'paypal-auth-version',
  'x-hub-signature',
  'x-github-event',
  'x-slack-signature',
]);

/**
 * Headers that should be partially redacted (show first/last few characters)
 */
const PARTIALLY_REDACTABLE_HEADERS = new Set([
  'user-agent',
  'referer',
  'origin',
  'x-forwarded-by',
  'via',
]);

/**
 * Headers that are safe to store as-is
 */
const SAFE_HEADERS = new Set([
  'accept',
  'accept-encoding',
  'accept-language',
  'cache-control',
  'connection',
  'content-type',
  'content-length',
  'content-encoding',
  'date',
  'etag',
  'expires',
  'host',
  'last-modified',
  'location',
  'pragma',
  'server',
  'vary',
  'www-authenticate',
  'x-powered-by',
  'x-frame-options',
  'x-content-type-options',
  'strict-transport-security',
  'content-security-policy',
  'x-ratelimit-limit',
  'x-ratelimit-remaining',
  'x-ratelimit-reset',
  'retry-after',
  'age',
  'allow',
  'access-control-allow-origin',
  'access-control-allow-methods',
  'access-control-allow-headers',
  'access-control-expose-headers',
  'access-control-max-age',
  'access-control-allow-credentials',
]);

export interface RedactionOptions {
  /** Include headers that are normally filtered out for debugging (admin only) */
  includeDebugHeaders?: boolean;
  /** Level of redaction: 'full' removes sensitive headers, 'partial' redacts them */
  redactionLevel?: 'full' | 'partial';
  /** Custom headers to redact */
  customSensitiveHeaders?: string[];
  /** Headers to preserve even if they're normally redacted */
  preserveHeaders?: string[];
}

export interface RedactionResult {
  headers: Record<string, string>;
  redactedCount: number;
  redactedHeaders: string[];
  partiallyRedactedHeaders: string[];
}

export class HeaderRedactionService {
  
  /**
   * Redact sensitive headers from a headers object
   */
  redactHeaders(
    headers: Record<string, string | string[]>,
    options: RedactionOptions = {}
  ): RedactionResult {
    const {
      includeDebugHeaders = false,
      redactionLevel = 'full',
      customSensitiveHeaders = [],
      preserveHeaders = [],
    } = options;

    const result: RedactionResult = {
      headers: {},
      redactedCount: 0,
      redactedHeaders: [],
      partiallyRedactedHeaders: [],
    };

    // Combine default sensitive headers with custom ones
    const allSensitiveHeaders = new Set([
      ...SENSITIVE_HEADERS,
      ...customSensitiveHeaders.map(h => h.toLowerCase()),
    ]);

    // Headers to preserve
    const preserveSet = new Set(preserveHeaders.map(h => h.toLowerCase()));

    for (const [key, value] of Object.entries(headers)) {
      const lowerKey = key.toLowerCase();
      const stringValue = Array.isArray(value) ? value.join(', ') : value;

      // Skip empty headers
      if (!stringValue || stringValue.trim() === '') {
        continue;
      }

      // Preserve headers that are explicitly marked for preservation
      if (preserveSet.has(lowerKey)) {
        result.headers[key] = stringValue;
        continue;
      }

      // Handle sensitive headers
      if (allSensitiveHeaders.has(lowerKey)) {
        result.redactedCount++;
        result.redactedHeaders.push(key);

        if (redactionLevel === 'partial' || includeDebugHeaders) {
          result.headers[key] = this.partiallyRedactValue(stringValue);
        }
        // If redactionLevel is 'full', we don't include the header at all
        continue;
      }

      // Handle partially redactable headers
      if (PARTIALLY_REDACTABLE_HEADERS.has(lowerKey)) {
        result.headers[key] = this.partiallyRedactValue(stringValue);
        result.partiallyRedactedHeaders.push(key);
        continue;
      }

      // Handle safe headers
      if (SAFE_HEADERS.has(lowerKey)) {
        result.headers[key] = stringValue;
        continue;
      }

      // For unknown headers, be conservative and redact them unless in debug mode
      if (includeDebugHeaders) {
        result.headers[key] = this.partiallyRedactValue(stringValue);
        result.partiallyRedactedHeaders.push(key);
      } else {
        result.redactedCount++;
        result.redactedHeaders.push(key);
      }
    }

    logger.debug('Headers redacted', {
      originalCount: Object.keys(headers).length,
      finalCount: Object.keys(result.headers).length,
      redactedCount: result.redactedCount,
      redactionLevel,
      includeDebugHeaders,
    });

    return result;
  }

  /**
   * Partially redact a header value (show first and last few characters)
   */
  private partiallyRedactValue(value: string): string {
    if (value.length <= 8) {
      return '*'.repeat(value.length);
    }

    const start = value.substring(0, 3);
    const end = value.substring(value.length - 3);
    const middle = '*'.repeat(Math.min(value.length - 6, 10));

    return `${start}${middle}${end}`;
  }

  /**
   * Redact sensitive data from request/response logs
   */
  redactLogData(data: any, options: RedactionOptions = {}): any {
    if (!data || typeof data !== 'object') {
      return data;
    }

    const redacted = { ...data };

    // Redact headers if present
    if (redacted.headers) {
      const result = this.redactHeaders(redacted.headers, options);
      redacted.headers = result.headers;
    }

    // Redact request headers
    if (redacted.request && redacted.request.headers) {
      const result = this.redactHeaders(redacted.request.headers, options);
      redacted.request.headers = result.headers;
    }

    // Redact response headers
    if (redacted.response && redacted.response.headers) {
      const result = this.redactHeaders(redacted.response.headers, options);
      redacted.response.headers = result.headers;
    }

    // Redact common sensitive fields in request/response bodies
    if (redacted.body || redacted.data) {
      const bodyData = redacted.body || redacted.data;
      if (typeof bodyData === 'object') {
        redacted.body = this.redactObjectData(bodyData);
        redacted.data = this.redactObjectData(bodyData);
      }
    }

    // Redact URL parameters that might contain sensitive data
    if (redacted.url && typeof redacted.url === 'string') {
      redacted.url = this.redactSensitiveUrlParams(redacted.url);
    }

    if (redacted.originalUrl && typeof redacted.originalUrl === 'string') {
      redacted.originalUrl = this.redactSensitiveUrlParams(redacted.originalUrl);
    }

    return redacted;
  }

  /**
   * Redact sensitive fields from object data
   */
  private redactObjectData(obj: any): any {
    if (!obj || typeof obj !== 'object') {
      return obj;
    }

    const sensitiveFields = new Set([
      'password',
      'secret',
      'token',
      'key',
      'authorization',
      'auth',
      'apikey',
      'api_key',
      'access_token',
      'refresh_token',
      'session',
      'cookie',
      'csrf',
      'xsrf',
    ]);

    const redacted = Array.isArray(obj) ? [] : {};

    for (const [key, value] of Object.entries(obj)) {
      const lowerKey = key.toLowerCase();
      
      if (sensitiveFields.has(lowerKey) || lowerKey.includes('password') || lowerKey.includes('secret')) {
        redacted[key] = typeof value === 'string' ? this.partiallyRedactValue(value) : '[REDACTED]';
      } else if (typeof value === 'object' && value !== null) {
        redacted[key] = this.redactObjectData(value);
      } else {
        redacted[key] = value;
      }
    }

    return redacted;
  }

  /**
   * Redact sensitive URL parameters
   */
  private redactSensitiveUrlParams(url: string): string {
    try {
      const urlObj = new URL(url);
      const sensitiveParams = new Set([
        'token',
        'key',
        'secret',
        'password',
        'auth',
        'authorization',
        'api_key',
        'apikey',
        'access_token',
        'session',
        'csrf',
        'xsrf',
      ]);

      for (const [key, value] of urlObj.searchParams.entries()) {
        if (sensitiveParams.has(key.toLowerCase()) || 
            key.toLowerCase().includes('password') || 
            key.toLowerCase().includes('secret') ||
            key.toLowerCase().includes('token')) {
          urlObj.searchParams.set(key, this.partiallyRedactValue(value));
        }
      }

      return urlObj.toString();
    } catch (error) {
      // If URL parsing fails, return original URL
      logger.warn('Failed to parse URL for redaction:', { url, error: error.message });
      return url;
    }
  }

  /**
   * Check if a header should be considered sensitive
   */
  isSensitiveHeader(headerName: string, customSensitive: string[] = []): boolean {
    const lowerName = headerName.toLowerCase();
    return SENSITIVE_HEADERS.has(lowerName) || 
           customSensitive.map(h => h.toLowerCase()).includes(lowerName);
  }

  /**
   * Get list of all headers that would be redacted
   */
  getSensitiveHeadersList(): string[] {
    return Array.from(SENSITIVE_HEADERS).sort();
  }

  /**
   * Validate redaction configuration
   */
  validateRedactionConfig(options: RedactionOptions): { valid: boolean; errors: string[] } {
    const errors: string[] = [];

    if (options.redactionLevel && !['full', 'partial'].includes(options.redactionLevel)) {
      errors.push('redactionLevel must be either "full" or "partial"');
    }

    if (options.customSensitiveHeaders) {
      if (!Array.isArray(options.customSensitiveHeaders)) {
        errors.push('customSensitiveHeaders must be an array');
      } else {
        for (const header of options.customSensitiveHeaders) {
          if (typeof header !== 'string') {
            errors.push('All customSensitiveHeaders must be strings');
            break;
          }
        }
      }
    }

    if (options.preserveHeaders) {
      if (!Array.isArray(options.preserveHeaders)) {
        errors.push('preserveHeaders must be an array');
      } else {
        for (const header of options.preserveHeaders) {
          if (typeof header !== 'string') {
            errors.push('All preserveHeaders must be strings');
            break;
          }
        }
      }
    }

    return {
      valid: errors.length === 0,
      errors,
    };
  }
}

// Singleton instance
export const headerRedactionService = new HeaderRedactionService();
