import { BaseRule } from './BaseRule.js';
import { Arbiter } from '../../core/Arbiter.js';
import { OWAQualitativeFusion, getOWAQualitativeWeights } from '../../qualitative/OWAQualitativeFusion.js';
import { QualitativeScale, DEFAULT_QUALITATIVE_SCALE } from '../../qualitative/QualitativeScale.js';
import { BilatticeOrderings } from '../../qualitative/BilatticeOrderings.js';
import { QualitativeCapacity } from '../../qualitative/QualitativeCapacity.js';

/**
 * QualitativeRelationalComparatorRule - Evaluates access by comparing qualitative values from relations,
 * treating values as qualitative intervals that "blur" over time based on decaying possibility.
 * 
 * This implementation uses qualitative scales and possibility theory instead of numeric intervals.
 * 
 * Configuration:
 * {
 *   type: 'relational_comparator',
 *   qualitative: true,            // Flag to indicate qualitative mode
 *   scaleName: string,           // Name of the qualitative scale to use (e.g., 'five-point', 'ternary')
 *   leftOperand: {
 *     rule: {...},               // Any rule configuration
 *     extractValue: true,        // Extract value from relation (if false, rule's possibility is used as value)
 *     valueRelation: string,     // Optional: specific relation for value
 *     aggregator: string,        // 'max', 'min', 'majority', 'priority', 'optimistic', etc.
 *     owaWeights: number[],     // Optional: custom OWA weights for aggregation
 *     decaySteps: number,        // Number of steps to decay per period on the qualitative scale
 *     decayPeriod: string,       // 'MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR' (default 'HOUR')
 *     possibilityDecayDirection: string, // 'down' (towards 0), 'neutral' (towards 0.5), 'up' (towards 1), 'stable' (no decay)
 *     valueBlurDirection: string, // 'neutral' (symmetric), 'down' (expands lower bound), 'up' (expands upper bound), 'stable' (minimal blur)
 *     baseBlurSteps: number,     // Number of steps to blur per possibility decay step
 *     minOperandPossibility: number, // If operand's decayed possibility < this, considered no value
 *     evaluateFrom: string       // 'user', 'object', or 'auto' (for rule evaluation perspective)
 *   },
 *   rightOperand: {...},         // Same structure as leftOperand
 *   comparator: string,          // '>', '>=', '<', '<=', '==', '!='
 *   marginSteps: number,         // Number of steps to shift right operand on the scale before blurring
 *   minRulePossibility: number,  // Optional: if final rule possibility < this, considered 0
 *   fallbackBehavior: string     // 'allow' or 'deny' if values/operands are insufficient
 * }
 */
export class QualitativeRelationalComparatorRule extends BaseRule {
  constructor(arbiter, ruleEvaluator) {
    super(arbiter);
    this.ruleEvaluator = ruleEvaluator;
  }

  /**
   * Evaluate qualitative relational comparison
   * @protected
   */
  _evaluateRule(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options) {
    const { 
      left: leftOperand, 
      right: rightOperand, 
      comparator, 
      marginSteps = 0, 
      fallbackBehavior = 'deny', 
      minRulePossibility = 0,
      scaleName = 'five-point'
    } = rule;
    
    try {
      // Get the qualitative scale
      const scale = this._getQualitativeScale(scaleName);
      
      const ruleMetaBase = {
        ruleType: 'QualitativeRelationalComparatorRule',
        userKey,
        objectKey,
        comparator,
        scaleName,
        marginStepsApplied: rightOperand ? marginSteps : 0,
        fallbackBehavior,
        minRulePossibilityUsed: minRulePossibility,
        evaluationStarted: Date.now()
      };
      
      let evaluationMeta = options.trackEvaluation ? { 
        ...ruleMetaBase, 
        leftOperandDetails: {}, 
        rightOperandDetails: {} 
      } : null;

      // Evaluate left operand
      const leftOpResult = this._evaluateOperand(
        userId, userKey, objectId, objectKey,
        leftOperand, visited, currentRelation, options, 'left', 0, evaluationMeta, scale // No margin for left
      );
      if (evaluationMeta) evaluationMeta.leftOperandDetails = leftOpResult.meta || {};
      
      // Evaluate right operand
      const rightOpResult = this._evaluateOperand(
        userId, userKey, objectId, objectKey,
        rightOperand, visited, currentRelation, options, 'right', marginSteps, evaluationMeta, scale // Apply margin for right
      );
      if (evaluationMeta) evaluationMeta.rightOperandDetails = rightOpResult.meta || {};
      
      // Perform comparison of qualitative intervals
      let comparisonOutput = this._compareBlurredValues(
        leftOpResult, rightOpResult, comparator, fallbackBehavior, rule, options, ruleMetaBase, scale
      );

      // Apply minimum rule possibility threshold
      if (scale.compare(comparisonOutput.possibility, minRulePossibility) < 0) {
        if (evaluationMeta && evaluationMeta.comparisonStep) {
          evaluationMeta.comparisonStep.finalPossibilityBeforeMinRule = comparisonOutput.possibility;
        }
        comparisonOutput.possibility = scale.bottom;
        comparisonOutput.reason = comparisonOutput.reason + '_belowMinRulePossibility';
        if (evaluationMeta && evaluationMeta.comparisonStep) {
          evaluationMeta.comparisonStep.adjustedToZeroByMinRule = true;
        }
      }
      
      if (evaluationMeta) {
        evaluationMeta.finalResult = { 
          possibility: comparisonOutput.possibility, 
          reliability: comparisonOutput.reliability, 
          reason: comparisonOutput.reason 
        };
        evaluationMeta.evaluationCompleted = Date.now();
        evaluationMeta.totalEvaluationTime = evaluationMeta.evaluationCompleted - evaluationMeta.evaluationStarted;
      }

      return {
        possibility: comparisonOutput.possibility,
        reliability: comparisonOutput.reliability,
        reason: comparisonOutput.reason,
        meta: evaluationMeta
      };
    } catch (error) {
      console.error('QualitativeRelationalComparatorRule evaluation error:', error);
      return {
        possibility: fallbackBehavior === 'allow' ? 1 : 0,
        reliability: 0,
        reason: 'error',
        meta: { error: error.message, fallbackBehavior }
      };
    }
  }

  /**
   * Get the qualitative scale by name
   * @private
   */
  _getQualitativeScale(scaleName) {
    switch (scaleName) {
      case 'binary':
        return QualitativeScale.binary();
      case 'ternary':
        return QualitativeScale.ternary();
      case 'five-point':
        return QualitativeScale.fivePoint();
      case 'ten-point':
        return QualitativeScale.tenPoint();
      default:
        console.warn(`Unknown scale name: ${scaleName}, using default five-point scale`);
        return DEFAULT_QUALITATIVE_SCALE;
    }
  }

  /**
   * Evaluate a single operand and return qualitative interval and possibility
   * @private
   */
  _evaluateOperand(userId, userKey, objectId, objectKey, operandConfig, visited, currentRelation, options, side, marginSteps, evaluationMeta, scale) {
    if (!operandConfig) {
      return {
        values: [],
        possibility: scale.bottom,
        meta: { error: `No ${side} operand configuration` }
      };
    }

    try {
      const operandMeta = options.trackEvaluation ? {
        side,
        operandConfig: { ...operandConfig },
        marginStepsApplied: marginSteps,
        evaluationStarted: Date.now()
      } : null;

      const evaluateFrom = operandConfig.evaluateFrom || 'auto';
      let evalUserId = userId;
      let evalUserKey = userKey;
      let evalObjectId = objectId;
      let evalObjectKey = objectKey;
      const nestedRuleConfig = operandConfig.rule;
      if (evaluateFrom === 'user') {
        if (nestedRuleConfig.type === 'direct' || nestedRuleConfig.type === 'computed') {
          evalUserId = userId;
          evalUserKey = userKey;
          evalObjectId = userId;
          evalObjectKey = userKey;
        } else if (!nestedRuleConfig.extractValues) {
          evalUserId = objectId;
          evalUserKey = objectKey;
          evalObjectId = userId;
          evalObjectKey = userKey;
        }
      } else if (evaluateFrom === 'object') {
        if (nestedRuleConfig.type === 'direct' || nestedRuleConfig.type === 'computed') {
          evalUserId = objectId;
          evalUserKey = objectKey;
          evalObjectId = objectId;
          evalObjectKey = objectKey;
        } else {
          evalUserId = objectId;
          evalUserKey = objectKey;
          evalObjectId = userId;
          evalObjectKey = userKey;
        }
      }
      if (operandMeta) {
        operandMeta.evaluateFrom = evaluateFrom;
        operandMeta.evalUserKey = evalUserKey;
        operandMeta.evalObjectKey = evalObjectKey;
      }

      // Evaluate the underlying rule
      const ruleResult = this.ruleEvaluator.evaluateRule(
        evalUserId, evalUserKey, evalObjectId, evalObjectKey, operandConfig.rule, visited, currentRelation, options
      );

      if (operandMeta) {
        operandMeta.ruleResult = { 
          possibility: ruleResult.possibility, 
          reliability: ruleResult.reliability, 
          reason: ruleResult.reason 
        };
      }

      // Extract values from the rule result
      const extractedValues = this._extractValuesFromRuleResult(ruleResult, operandConfig, scale);
      
      if (operandMeta) {
        operandMeta.extractedValues = extractedValues;
      }

      // Apply margin of safety (shift on the scale)
      const adjustedValues = this._applyMarginSteps(extractedValues, marginSteps, scale);
      
      if (operandMeta) {
        operandMeta.adjustedValues = adjustedValues;
      }

      // Extract blurred values with qualitative decay and blur
      const blurredValues = this._extractBlurredValues(adjustedValues, operandConfig, scale, options);
      
      if (operandMeta) {
        operandMeta.blurredValues = blurredValues;
        operandMeta.evaluationCompleted = Date.now();
        operandMeta.totalEvaluationTime = operandMeta.evaluationCompleted - operandMeta.evaluationStarted;
      }

      // Aggregate the blurred values using qualitative OWA
      const aggregatedResult = this._aggregateCrispValues(blurredValues, operandConfig, scale);

      return {
        values: blurredValues,
        possibility: aggregatedResult.possibility,
        meta: operandMeta
      };
    } catch (error) {
      console.error(`Error evaluating ${side} operand:`, error);
      return {
        values: [],
        possibility: scale.bottom,
        meta: { error: error.message, side }
      };
    }
  }

  /**
   * Extract values from rule result and convert to qualitative scale
   * @private
   */
  _extractValuesFromRuleResult(ruleResult, operandConfig, scale) {
    const values = [];
    
    if (operandConfig.extractValue !== false && (ruleResult.values || ruleResult.collectedValues)) {
      const collected = ruleResult.values || ruleResult.collectedValues;
      // Extract actual values from relations
      for (const valueObj of collected) {
        if (valueObj.value !== undefined) {
          // Convert numeric value to closest qualitative scale value
          const qualitativeValue = this._convertToQualitativeValue(valueObj.value, scale);
          values.push({
            value: qualitativeValue,
            possibility: this._convertToQualitativeValue(valueObj.possibility || 1, scale),
            timestamp: valueObj.timestamp,
            relation: valueObj.relation,
            meta: valueObj.meta
          });
        }
      }
    } else {
      // Use rule's possibility as the value
      const qualitativeValue = this._convertToQualitativeValue(ruleResult.possibility, scale);
      values.push({
        value: qualitativeValue,
        possibility: qualitativeValue,
        timestamp: options && options.now !== undefined && options.now !== null ? options.now : Date.now(),
        relation: 'rule_result',
        meta: { source: 'rule_possibility' }
      });
    }

    return values;
  }

  /**
   * Convert a numeric value to the closest qualitative scale value
   * @private
   */
  _convertToQualitativeValue(numericValue, scale) {
    // Find the closest value in the scale
    let closestValue = scale.bottom;
    let minDistance = Math.abs(numericValue - scale.bottom);
    
    for (const scaleValue of scale.values) {
      const distance = Math.abs(numericValue - scaleValue);
      if (distance < minDistance) {
        minDistance = distance;
        closestValue = scaleValue;
      }
    }
    
    return closestValue;
  }

  /**
   * Apply margin steps to shift values on the qualitative scale
   * @private
   */
  _applyMarginSteps(values, marginSteps, scale) {
    if (marginSteps === 0) return values;
    
    return values.map(valueObj => {
      const currentIndex = scale.indexOf(valueObj.value);
      const newIndex = Math.max(0, Math.min(scale.size - 1, currentIndex + marginSteps));
      const newValue = scale.at(newIndex);
      
      return {
        ...valueObj,
        value: newValue
      };
    });
  }

  /**
   * Extract blurred values with qualitative decay and blur
   * @private
   */
  _extractBlurredValues(values, operandConfig, scale, options = null) {
    const {
      decaySteps = 1,
      decayPeriod = 'HOUR',
      possibilityDecayDirection = 'down',
      valueBlurDirection = 'neutral',
      baseBlurSteps = 1,
      minOperandPossibility = 0
    } = operandConfig;

    const blurredValues = [];

    for (const valueObj of values) {
      const pointValue = valueObj.value;
      const initialPossibility = valueObj.possibility;
      const timestamp = valueObj.timestamp ||
        (options && options.now !== undefined && options.now !== null ? options.now : Date.now());
      
      // Calculate periods elapsed
      const periodsElapsed = this._calculatePeriodsElapsed(timestamp, decayPeriod, options);
      
      // Calculate decayed possibility
      const decayedPossibility = this._calculateDecayedPossibility(
        initialPossibility, periodsElapsed, decaySteps, possibilityDecayDirection, scale
      );
      
      // Skip if possibility is too low
      if (scale.compare(decayedPossibility, minOperandPossibility) < 0) {
        continue;
      }
      
      // Calculate blur amount based on possibility loss
      const possibilityLossSteps = this._calculatePossibilityLossSteps(
        initialPossibility, decayedPossibility, scale
      );
      const blurSteps = Math.floor(possibilityLossSteps * baseBlurSteps);
      
      // Create qualitative interval
      const blurredInterval = this._createQualitativeInterval(
        pointValue, blurSteps, valueBlurDirection, scale
      );
      
      blurredValues.push({
        interval: blurredInterval,
        possibility: decayedPossibility,
        originalValue: pointValue,
        originalPossibility: initialPossibility,
        timestamp,
        relation: valueObj.relation,
        meta: {
          ...valueObj.meta,
          periodsElapsed,
          possibilityLossSteps,
          blurSteps
        }
      });
    }

    return blurredValues;
  }

  /**
   * Calculate periods elapsed since timestamp
   * @private
   */
  _calculatePeriodsElapsed(timestamp, decayPeriod, options = null) {
    const now = options && options.now !== undefined && options.now !== null ? options.now : Date.now();
    const elapsed = now - timestamp;
    
    const periodMs = {
      'MINUTE': 60 * 1000,
      'HOUR': 60 * 60 * 1000,
      'DAY': 24 * 60 * 60 * 1000,
      'WEEK': 7 * 24 * 60 * 60 * 1000,
      'MONTH': 30 * 24 * 60 * 60 * 1000,
      'YEAR': 365 * 24 * 60 * 60 * 1000
    };
    
    return Math.floor(elapsed / (periodMs[decayPeriod] || periodMs['HOUR']));
  }

  /**
   * Calculate decayed possibility using qualitative scale steps
   * @private
   */
  _calculateDecayedPossibility(initialPossibility, periodsElapsed, decaySteps, direction, scale) {
    const initialIndex = scale.indexOf(initialPossibility);
    const totalDecaySteps = periodsElapsed * decaySteps;
    
    let newIndex;
    switch (direction) {
      case 'down':
        newIndex = Math.max(0, initialIndex - totalDecaySteps);
        break;
      case 'up':
        newIndex = Math.min(scale.size - 1, initialIndex + totalDecaySteps);
        break;
      case 'neutral':
        const targetIndex = Math.floor(scale.size / 2); // Middle of scale
        if (initialIndex > targetIndex) {
          newIndex = Math.max(targetIndex, initialIndex - totalDecaySteps);
        } else {
          newIndex = Math.min(targetIndex, initialIndex + totalDecaySteps);
        }
        break;
      case 'stable':
      default:
        newIndex = initialIndex;
        break;
    }
    
    return scale.at(newIndex);
  }

  /**
   * Calculate the number of steps of possibility loss
   * @private
   */
  _calculatePossibilityLossSteps(initialPossibility, decayedPossibility, scale) {
    const initialIndex = scale.indexOf(initialPossibility);
    const decayedIndex = scale.indexOf(decayedPossibility);
    return Math.abs(initialIndex - decayedIndex);
  }

  /**
   * Create a qualitative interval by blurring around a point value
   * @private
   */
  _createQualitativeInterval(pointValue, blurSteps, direction, scale) {
    const pointIndex = scale.indexOf(pointValue);
    
    let lowerIndex, upperIndex;
    switch (direction) {
      case 'down':
        lowerIndex = Math.max(0, pointIndex - blurSteps);
        upperIndex = pointIndex;
        break;
      case 'up':
        lowerIndex = pointIndex;
        upperIndex = Math.min(scale.size - 1, pointIndex + blurSteps);
        break;
      case 'neutral':
      default:
        lowerIndex = Math.max(0, pointIndex - blurSteps);
        upperIndex = Math.min(scale.size - 1, pointIndex + blurSteps);
        break;
    }
    
    return {
      lower: scale.at(lowerIndex),
      upper: scale.at(upperIndex)
    };
  }

  /**
   * Aggregate blurred values using qualitative OWA fusion with optional bilattice reasoning
   * @private
   */
  _aggregateCrispValues(blurredValues, operandConfig, scale) {
    if (blurredValues.length === 0) {
      return { possibility: scale.bottom };
    }
    
    if (blurredValues.length === 1) {
      return { possibility: blurredValues[0].possibility };
    }
    
    const aggregator = operandConfig.aggregator || 'max';
    const useBilattice = operandConfig.useBilattice || false;
    const epistemicMode = operandConfig.epistemicMode || 'hybrid';
    const capacityType = operandConfig.capacityType || 'simple_support';
    
    // Convert blurred values to collected values format for bilattice analysis
    const collectedValues = blurredValues.map((bv, index) => ({
      value: bv.possibility, // Use possibility as the value for bilattice analysis
      possibility: bv.possibility,
      path: [`blurred_value_${index}`],
      source: {
        entityKey: 'qualitative_operand',
        relation: 'blurred_value',
        step: index
      },
      metadata: {
        timestamp: bv.timestamp ||
          (options && options.now !== undefined && options.now !== null ? options.now : Date.now()),
        reliability: 1.0,
        originalValue: bv.originalValue,
        originalPossibility: bv.originalPossibility,
        interval: bv.interval,
        blurSteps: bv.meta?.blurSteps,
        periodsElapsed: bv.meta?.periodsElapsed
      }
    }));
    
    // Use bilattice-enhanced evidence combination if enabled
    if (useBilattice) {
      const capacity = this._createCapacityFromValues(collectedValues, scale, capacityType);
      const bilatticeResult = this._combineEvidenceWithBilattice(collectedValues, {
        method: aggregator,
        useBilattice: true,
        capacity: capacity,
        scale: scale,
        epistemicMode: epistemicMode
      });
      
      return { 
        possibility: bilatticeResult.value,
        epistemicAnalysis: bilatticeResult.epistemicAnalysis,
        aggregationMethod: `bilattice_${epistemicMode}`
      };
    }
    
    // Standard qualitative OWA fusion
    const values = blurredValues.map(v => v.possibility);
    const metas = blurredValues.map(v => v.meta);
    
    // Generate OWA weights
    const weights = getOWAQualitativeWeights(aggregator, values.length, null, scale);
    
    // Perform qualitative OWA fusion
    const result = OWAQualitativeFusion.fuseWithMeta(values, weights, weights, aggregator, scale);
    
    return { 
      possibility: result.value,
      aggregationMethod: `owa_${aggregator}`
    };
  }

  /**
   * Compare blurred qualitative intervals
   * @private
   */
  _compareBlurredValues(leftResult, rightResult, comparator, fallbackBehavior, rule, options, ruleMetaBase, scale) {
    const leftValues = leftResult.values || [];
    const rightValues = rightResult.values || [];
    
    if (leftValues.length === 0 || rightValues.length === 0) {
      return {
        possibility: fallbackBehavior === 'allow' ? scale.top : scale.bottom,
        reliability: 0,
        reason: 'insufficient_values'
      };
    }
    
    // Compare all combinations of left and right intervals
    const comparisonResults = [];
    
    for (const leftValue of leftValues) {
      for (const rightValue of rightValues) {
        const comparisonPossibility = this._calculateQualitativeIntervalComparison(
          leftValue.interval, rightValue.interval, comparator, scale
        );
        
        // Combine with confidence weights (min operation in qualitative logic)
        const combinedPossibility = scale.min(comparisonPossibility, scale.min(leftValue.possibility, rightValue.possibility));
        
        comparisonResults.push({
          possibility: combinedPossibility,
          leftInterval: leftValue.interval,
          rightInterval: rightValue.interval,
          leftPossibility: leftValue.possibility,
          rightPossibility: rightValue.possibility
        });
      }
    }
    
    // Take the maximum possibility across all comparisons
    const maxPossibility = scale.maxAll(comparisonResults.map(r => r.possibility));
    
    return {
      possibility: maxPossibility,
      reliability: 1, // Qualitative comparisons are considered fully reliable
      reason: 'qualitative_interval_comparison'
    };
  }

  /**
   * Calculate qualitative interval comparison using possibility theory
   * @private
   */
  _calculateQualitativeIntervalComparison(leftInterval, rightInterval, comparator, scale) {
    const { lower: lLower, upper: lUpper } = leftInterval;
    const { lower: rLower, upper: rUpper } = rightInterval;
    
    switch (comparator) {
      case '>':
        // Possibility(L > R): Is it possible that a value from L is greater than a value from R?
        // This is true if the top of L is greater than the bottom of R
        return scale.compare(lUpper, rLower) > 0 ? scale.top : scale.bottom;
        
      case '>=':
        // Possibility(L >= R): Is it possible that a value from L is >= a value from R?
        return scale.compare(lUpper, rLower) >= 0 ? scale.top : scale.bottom;
        
      case '<':
        // Possibility(L < R): Is it possible that a value from L is less than a value from R?
        // This is true if the bottom of L is less than the top of R
        return scale.compare(lLower, rUpper) < 0 ? scale.top : scale.bottom;
        
      case '<=':
        // Possibility(L <= R): Is it possible that a value from L is <= a value from R?
        return scale.compare(lLower, rUpper) <= 0 ? scale.top : scale.bottom;
        
      case '==':
        // Possibility(L == R): Is it possible that intervals overlap?
        // This is true if there's any overlap between the intervals
        return (scale.compare(lUpper, rLower) >= 0 && scale.compare(lLower, rUpper) <= 0) ? scale.top : scale.bottom;
        
      case '!=':
        // Possibility(L != R): Is it possible that intervals don't overlap?
        // This is true if there's no overlap between the intervals
        return (scale.compare(lUpper, rLower) < 0 || scale.compare(lLower, rUpper) > 0) ? scale.top : scale.bottom;
        
      default:
        console.warn(`Unknown comparator: ${comparator}`);
        return scale.bottom;
    }
  }
}
