import type { IMatcher, IHeaderMatchOptions } from '../types.js';

/**
 * HeaderMatcher provides HTTP header matching functionality
 * Supporting exact matches, patterns, and case-insensitive matching
 */
export class HeaderMatcher implements IMatcher<boolean, IHeaderMatchOptions> {
  /**
   * Match a header value against a pattern
   * @param pattern The pattern to match
   * @param value The header value to test
   * @param options Matching options
   * @returns true if the value matches the pattern
   */
  static match(
    pattern: string,
    value: string | undefined,
    options: IHeaderMatchOptions = {}
  ): boolean {
    // Handle missing header
    if (value === undefined || value === null) {
      return pattern === '' || pattern === null || pattern === undefined;
    }

    // Convert to string and normalize
    const normalizedPattern = String(pattern);
    const normalizedValue = String(value);

    // Apply case sensitivity
    const comparePattern = options.caseInsensitive !== false
      ? normalizedPattern.toLowerCase()
      : normalizedPattern;
    const compareValue = options.caseInsensitive !== false
      ? normalizedValue.toLowerCase()
      : normalizedValue;

    // Exact match
    if (options.exactMatch !== false) {
      return comparePattern === compareValue;
    }

    // Pattern matching (simple wildcard support)
    if (comparePattern.includes('*')) {
      const regex = new RegExp(
        '^' + comparePattern.replace(/\*/g, '.*') + '$',
        options.caseInsensitive !== false ? 'i' : ''
      );
      return regex.test(normalizedValue);
    }

    // Contains match (if not exact match mode)
    return compareValue.includes(comparePattern);
  }

  /**
   * Match multiple headers against a set of required headers
   * @param requiredHeaders Headers that must match
   * @param actualHeaders Actual request headers
   * @param options Matching options
   * @returns true if all required headers match
   */
  static matchAll(
    requiredHeaders: Record<string, string>,
    actualHeaders: Record<string, string | string[] | undefined>,
    options: IHeaderMatchOptions = {}
  ): boolean {
    for (const [name, pattern] of Object.entries(requiredHeaders)) {
      const headerName = options.caseInsensitive !== false
        ? name.toLowerCase()
        : name;

      // Find the actual header (case-insensitive search if needed)
      let actualValue: string | undefined;
      if (options.caseInsensitive !== false) {
        const actualKey = Object.keys(actualHeaders).find(
          key => key.toLowerCase() === headerName
        );
        const rawValue = actualKey ? actualHeaders[actualKey] : undefined;
        // Handle array values (multiple headers with same name)
        actualValue = Array.isArray(rawValue) ? rawValue.join(', ') : rawValue;
      } else {
        const rawValue = actualHeaders[name];
        // Handle array values (multiple headers with same name)
        actualValue = Array.isArray(rawValue) ? rawValue.join(', ') : rawValue;
      }

      // Check if this header matches
      if (!this.match(pattern, actualValue, options)) {
        return false;
      }
    }

    return true;
  }

  /**
   * Calculate the specificity of header requirements
   * More headers = more specific
   */
  static calculateSpecificity(headers: Record<string, string>): number {
    const count = Object.keys(headers).length;
    let score = count * 10;

    // Bonus for headers without wildcards (more specific)
    for (const value of Object.values(headers)) {
      if (!value.includes('*')) {
        score += 5;
      }
    }

    return score;
  }

  /**
   * Instance method for interface compliance
   */
  match(pattern: string, value: string, options?: IHeaderMatchOptions): boolean {
    return HeaderMatcher.match(pattern, value, options);
  }
}