// JSON Expression Evaluator
// Evaluates JSON expressions for policy conditions

/**
 * Evaluate a JSON expression condition
 * Supports simple comparison operators and logical operators
 * 
 * Example expressions:
 * {
 *   "and": [
 *     { "gt": { "risk_score": 0.7 } },
 *     { "eq": { "kyc_level": "verified" } }
 *   ]
 * }
 * 
 * {
 *   "or": [
 *     { "in": { "region": ["US", "EU"] } },
 *     { "eq": { "channel": "API" } }
 *   ]
 * }
 */
export class ExpressionEvaluator {
  /**
   * Evaluate a condition expression
   */
  evaluate(condition: Record<string, unknown>, context: Record<string, unknown>): boolean {
    if (!condition || typeof condition !== 'object') {
      return false;
    }

    // Handle logical operators
    if ('and' in condition) {
      const andConditions = condition.and as Array<Record<string, unknown>>;
      return andConditions.every((cond) => this.evaluate(cond, context));
    }

    if ('or' in condition) {
      const orConditions = condition.or as Array<Record<string, unknown>>;
      return orConditions.some((cond) => this.evaluate(cond, context));
    }

    if ('not' in condition) {
      const notCondition = condition.not as Record<string, unknown>;
      return !this.evaluate(notCondition, context);
    }

    // Handle comparison operators
    if ('eq' in condition) {
      const eq = condition.eq as Record<string, unknown>;
      return this.evaluateComparison(eq, context, (a, b) => a === b);
    }

    if ('ne' in condition) {
      const ne = condition.ne as Record<string, unknown>;
      return this.evaluateComparison(ne, context, (a, b) => a !== b);
    }

    if ('gt' in condition) {
      const gt = condition.gt as Record<string, unknown>;
      return this.evaluateComparison(gt, context, (a, b) => Number(a) > Number(b));
    }

    if ('gte' in condition) {
      const gte = condition.gte as Record<string, unknown>;
      return this.evaluateComparison(gte, context, (a, b) => Number(a) >= Number(b));
    }

    if ('lt' in condition) {
      const lt = condition.lt as Record<string, unknown>;
      return this.evaluateComparison(lt, context, (a, b) => Number(a) < Number(b));
    }

    if ('lte' in condition) {
      const lte = condition.lte as Record<string, unknown>;
      return this.evaluateComparison(lte, context, (a, b) => Number(a) <= Number(b));
    }

    if ('in' in condition) {
      const inCondition = condition.in as Record<string, unknown>;
      return this.evaluateIn(inCondition, context);
    }

    if ('contains' in condition) {
      const contains = condition.contains as Record<string, unknown>;
      return this.evaluateContains(contains, context);
    }

    // Default: treat as equality check
    return false;
  }

  /**
   * Evaluate a comparison operator
   */
  private evaluateComparison(
    condition: Record<string, unknown>,
    context: Record<string, unknown>,
    compareFn: (a: unknown, b: unknown) => boolean
  ): boolean {
    for (const [key, expectedValue] of Object.entries(condition)) {
      const actualValue = this.getNestedValue(context, key);
      if (!compareFn(actualValue, expectedValue)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Evaluate "in" operator (value in array)
   */
  private evaluateIn(condition: Record<string, unknown>, context: Record<string, unknown>): boolean {
    for (const [key, expectedArray] of Object.entries(condition)) {
      const actualValue = this.getNestedValue(context, key);
      if (!Array.isArray(expectedArray) || !expectedArray.includes(actualValue)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Evaluate "contains" operator (array contains value)
   */
  private evaluateContains(
    condition: Record<string, unknown>,
    context: Record<string, unknown>
  ): boolean {
    for (const [key, expectedValue] of Object.entries(condition)) {
      const actualValue = this.getNestedValue(context, key);
      if (!Array.isArray(actualValue) || !actualValue.includes(expectedValue)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Get nested value from context (supports dot notation)
   */
  private getNestedValue(obj: Record<string, unknown>, path: string): unknown {
    const keys = path.split('.');
    let value: unknown = obj;

    for (const key of keys) {
      if (value && typeof value === 'object' && key in value) {
        value = (value as Record<string, unknown>)[key];
      } else {
        return undefined;
      }
    }

    return value;
  }
}

export const expressionEvaluator = new ExpressionEvaluator();
