/**
 * Service: EligibilityEvaluator
 * 
 * Pure domain service for DSL-based eligibility evaluation.
 * Parses DSL expressions and evaluates them against rating data.
 * Provides explainable results with detailed reasons.
 */

import {
    EligibilityCondition,
    EligibilityFilterDto,
    EvaluationReason,
    EvaluationResultDto,
    ParsedEligibilityFilter
} from '../types/Eligibility';

export interface RatingData {
  platform: {
    [dimension: string]: number;
  };
  external: {
    [game: string]: {
      [type: string]: number;
    };
  };
}

export class EligibilityEvaluator {
  /**
   * Main entry point: evaluate DSL against rating data
   */
  evaluate(filter: EligibilityFilterDto, ratingData: RatingData): EvaluationResultDto {
    try {
      const parsed = this.parseDSL(filter.dsl);
      const reasons: EvaluationReason[] = [];
      
      // Evaluate each condition
      for (const condition of parsed.conditions) {
        const reason = this.evaluateCondition(condition, ratingData);
        reasons.push(reason);
      }
      
      // Determine overall eligibility based on logical operator
      const eligible = parsed.logicalOperator === 'AND'
        ? reasons.every(r => !r.failed)
        : reasons.some(r => !r.failed);
      
      // Build summary
      const summary = this.buildSummary(eligible, reasons, parsed.logicalOperator);
      
      const metadata: Record<string, unknown> = {
        filter: filter.dsl,
      };
      
      if (filter.context?.userId) {
        metadata.userId = filter.context.userId;
      }
      
      return {
        eligible,
        reasons,
        summary,
        evaluatedAt: new Date().toISOString(),
        metadata,
      };
    } catch (error) {
      // Handle parsing errors
      const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
      
      const metadata: Record<string, unknown> = {
        filter: filter.dsl,
        error: errorMessage,
      };
      
      if (filter.context?.userId) {
        metadata.userId = filter.context.userId;
      }
      
      return {
        eligible: false,
        reasons: [],
        summary: `Failed to evaluate filter: ${errorMessage}`,
        evaluatedAt: new Date().toISOString(),
        metadata,
      };
    }
  }

  /**
   * Parse DSL string into structured conditions
   * Supports: platform.{dim} >= 55 OR external.{game}.{type} between 2000 2500
   */
  parseDSL(dsl: string): ParsedEligibilityFilter {
    // Normalize and tokenize
    const normalized = dsl.trim().replace(/\s+/g, ' ');
    
    // Determine logical operator
    const hasOR = normalized.toUpperCase().includes(' OR ');
    const hasAND = normalized.toUpperCase().includes(' AND ');
    
    if (hasOR && hasAND) {
      throw new Error('Mixed AND/OR not supported. Use parentheses or separate filters.');
    }
    
    const logicalOperator = hasOR ? 'OR' : 'AND';
    const separator = hasOR ? ' OR ' : ' AND ';
    
    // Split into individual conditions
    const conditionStrings = normalized.split(separator).map(s => s.trim());
    
    const conditions: EligibilityCondition[] = conditionStrings.map(str => {
      return this.parseCondition(str);
    });
    
    return {
      conditions,
      logicalOperator,
    };
  }

  /**
   * Parse a single condition string
   * Examples:
   * - "platform.driving >= 55"
   * - "external.iracing.iRating between 2000 2500"
   */
  parseCondition(conditionStr: string): EligibilityCondition {
    // Check for "between" operator
    const betweenMatch = conditionStr.match(/^(.+?)\s+between\s+(\d+)\s+(\d+)$/i);
    if (betweenMatch) {
      const targetExpr = betweenMatch[1]?.trim();
      const minStr = betweenMatch[2];
      const maxStr = betweenMatch[3];
      
      if (!targetExpr || !minStr || !maxStr) {
        throw new Error(`Invalid between condition: "${conditionStr}"`);
      }
      
      const parsed = this.parseTargetExpression(targetExpr);
      
      return {
        target: parsed.target,
        dimension: parsed.dimension,
        game: parsed.game,
        operator: 'between',
        expected: [parseFloat(minStr), parseFloat(maxStr)],
      } as unknown as EligibilityCondition;
    }
    
    // Check for comparison operators
    const compareMatch = conditionStr.match(/^(.+?)\s*(>=|<=|>|<|=|!=)\s*(\d+(?:\.\d+)?)$/);
    if (compareMatch) {
      const targetExpr = compareMatch[1]?.trim();
      const operator = compareMatch[2];
      const valueStr = compareMatch[3];
      
      if (!targetExpr || !operator || !valueStr) {
        throw new Error(`Invalid comparison condition: "${conditionStr}"`);
      }
      
      const parsed = this.parseTargetExpression(targetExpr);
      
      return {
        target: parsed.target,
        dimension: parsed.dimension,
        game: parsed.game,
        operator: operator as EligibilityCondition['operator'],
        expected: parseFloat(valueStr),
      } as unknown as EligibilityCondition;
    }
    
    throw new Error(`Invalid condition format: "${conditionStr}"`);
  }

  /**
   * Parse target expression like "platform.driving" or "external.iracing.iRating"
   */
  private parseTargetExpression(expr: string): { target: 'platform' | 'external'; dimension?: string; game?: string } {
    const parts = expr.split('.');
    
    if (parts[0] === 'platform') {
      if (parts.length !== 2) {
        throw new Error(`Invalid platform expression: "${expr}"`);
      }
      const dimension = parts[1];
      if (!dimension) {
        throw new Error(`Invalid platform expression: "${expr}"`);
      }
      return { target: 'platform', dimension };
    }
    
    if (parts[0] === 'external') {
      if (parts.length !== 3) {
        throw new Error(`Invalid external expression: "${expr}"`);
      }
      const game = parts[1];
      const dimension = parts[2];
      if (!game || !dimension) {
        throw new Error(`Invalid external expression: "${expr}"`);
      }
      return { target: 'external', game, dimension };
    }
    
    throw new Error(`Unknown target: "${parts[0]}"`);
  }

  /**
   * Evaluate a single condition against rating data
   */
  private evaluateCondition(condition: EligibilityCondition, ratingData: RatingData): EvaluationReason {
    // Get actual value
    let actual: number | undefined;
    
    if (condition.target === 'platform' && condition.dimension) {
      actual = ratingData.platform[condition.dimension];
    } else if (condition.target === 'external' && condition.game && condition.dimension) {
      actual = ratingData.external[condition.game]?.[condition.dimension];
    }
    
    // Handle missing data
    if (actual === undefined || actual === null || isNaN(actual)) {
      return {
        target: condition.target === 'platform' 
          ? `platform.${condition.dimension}`
          : `external.${condition.game}.${condition.dimension}`,
        operator: condition.operator,
        expected: condition.expected,
        actual: 0,
        failed: true,
        message: `Missing data for ${condition.target === 'platform' ? `platform dimension "${condition.dimension}"` : `external game "${condition.game}" type "${condition.dimension}"`}`,
      };
    }
    
    // Evaluate based on operator
    let failed = false;
    
    switch (condition.operator) {
      case '>=':
        failed = actual < (condition.expected as number);
        break;
      case '<=':
        failed = actual > (condition.expected as number);
        break;
      case '>':
        failed = actual <= (condition.expected as number);
        break;
      case '<':
        failed = actual >= (condition.expected as number);
        break;
      case '=':
        failed = actual !== (condition.expected as number);
        break;
      case '!=':
        failed = actual === (condition.expected as number);
        break;
      case 'between': {
        const [min, max] = condition.expected as [number, number];
        failed = actual < min || actual > max;
        break;
      }
      default:
        throw new Error(`Unknown operator: ${condition.operator}`);
    }
    
    const targetStr = condition.target === 'platform' 
      ? `platform.${condition.dimension}`
      : `external.${condition.game}.${condition.dimension}`;
    
    const expectedStr = condition.operator === 'between'
      ? `${(condition.expected as [number, number])[0]} to ${(condition.expected as [number, number])[1]}`
      : `${condition.operator} ${condition.expected}`;
    
    return {
      target: targetStr,
      operator: condition.operator,
      expected: condition.expected,
      actual,
      failed,
      message: failed
        ? `Expected ${targetStr} ${expectedStr}, but got ${actual}`
        : `${targetStr} ${expectedStr} ✓`,
    };
  }

  /**
   * Build human-readable summary
   */
  private buildSummary(eligible: boolean, reasons: EvaluationReason[], operator: 'AND' | 'OR'): string {
    const failedReasons = reasons.filter(r => r.failed);
    
    if (eligible) {
      if (operator === 'OR') {
        return `Eligible: At least one condition met (${reasons.filter(r => !r.failed).length}/${reasons.length})`;
      }
      return `Eligible: All conditions met (${reasons.length}/${reasons.length})`;
    }
    
    if (operator === 'AND') {
      return `Not eligible: ${failedReasons.length} condition(s) failed`;
    }
    
    return `Not eligible: All conditions failed (${failedReasons.length}/${reasons.length})`;
  }
}
