import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
import type { IRouteSpecificity } from './types.js';
import { DomainMatcher, PathMatcher, IpMatcher, HeaderMatcher } from './matchers/index.js';

/**
 * Unified route specificity calculator
 * Provides consistent specificity scoring across all routing components
 */
export class RouteSpecificity {
  /**
   * Calculate the total specificity score for a route
   * Higher scores indicate more specific routes that should match first
   */
  static calculate(route: IRouteConfig): IRouteSpecificity {
    const specificity: IRouteSpecificity = {
      pathSpecificity: 0,
      domainSpecificity: 0,
      ipSpecificity: 0,
      headerSpecificity: 0,
      tlsSpecificity: 0,
      totalScore: 0
    };

    // Path specificity
    if (route.match.path) {
      specificity.pathSpecificity = PathMatcher.calculateSpecificity(route.match.path);
    }

    // Domain specificity
    if (route.match.domains) {
      const domains = Array.isArray(route.match.domains) 
        ? route.match.domains 
        : [route.match.domains];
      
      // Use the highest specificity among all domains
      specificity.domainSpecificity = Math.max(
        ...domains.map(d => DomainMatcher.calculateSpecificity(d))
      );
    }

    // IP specificity (clientIp is an array of IPs)
    if (route.match.clientIp && route.match.clientIp.length > 0) {
      // Use the first IP pattern for specificity calculation
      specificity.ipSpecificity = IpMatcher.calculateSpecificity(route.match.clientIp[0]);
    }

    // Header specificity (convert RegExp values to strings)
    if (route.match.headers) {
      const stringHeaders: Record<string, string> = {};
      for (const [key, value] of Object.entries(route.match.headers)) {
        stringHeaders[key] = value instanceof RegExp ? value.source : value;
      }
      specificity.headerSpecificity = HeaderMatcher.calculateSpecificity(stringHeaders);
    }

    // TLS version specificity
    if (route.match.tlsVersion && route.match.tlsVersion.length > 0) {
      specificity.tlsSpecificity = route.match.tlsVersion.length * 10;
    }

    // Calculate total score with weights
    specificity.totalScore = 
      specificity.pathSpecificity * 3 +      // Path is most important
      specificity.domainSpecificity * 2 +    // Domain is second
      specificity.ipSpecificity * 1.5 +      // IP is moderately important
      specificity.headerSpecificity * 1 +    // Headers are less important
      specificity.tlsSpecificity * 0.5;     // TLS is least important

    return specificity;
  }

  /**
   * Compare two routes and determine which is more specific
   * @returns positive if route1 is more specific, negative if route2 is more specific, 0 if equal
   */
  static compare(route1: IRouteConfig, route2: IRouteConfig): number {
    const spec1 = this.calculate(route1);
    const spec2 = this.calculate(route2);

    // First compare by total score
    if (spec1.totalScore !== spec2.totalScore) {
      return spec1.totalScore - spec2.totalScore;
    }

    // If total scores are equal, compare by individual components
    // Path is most important tiebreaker
    if (spec1.pathSpecificity !== spec2.pathSpecificity) {
      return spec1.pathSpecificity - spec2.pathSpecificity;
    }

    // Then domain
    if (spec1.domainSpecificity !== spec2.domainSpecificity) {
      return spec1.domainSpecificity - spec2.domainSpecificity;
    }

    // Then IP
    if (spec1.ipSpecificity !== spec2.ipSpecificity) {
      return spec1.ipSpecificity - spec2.ipSpecificity;
    }

    // Then headers
    if (spec1.headerSpecificity !== spec2.headerSpecificity) {
      return spec1.headerSpecificity - spec2.headerSpecificity;
    }

    // Finally TLS
    return spec1.tlsSpecificity - spec2.tlsSpecificity;
  }

  /**
   * Sort routes by specificity (most specific first)
   */
  static sort(routes: IRouteConfig[]): IRouteConfig[] {
    return [...routes].sort((a, b) => this.compare(b, a));
  }

  /**
   * Find the most specific route from a list
   */
  static findMostSpecific(routes: IRouteConfig[]): IRouteConfig | null {
    if (routes.length === 0) return null;
    
    return routes.reduce((most, current) => 
      this.compare(current, most) > 0 ? current : most
    );
  }

  /**
   * Check if a route has any matching criteria
   */
  static hasMatchCriteria(route: IRouteConfig): boolean {
    const match = route.match;
    return !!(
      match.domains ||
      match.path ||
      match.clientIp?.length ||
      match.headers ||
      match.tlsVersion?.length
    );
  }
}