import { OWAFusion } from '../../utils/OWAFusion.js';
import { buildValidity, normalizeValidity, DEFAULT_VALIDITY } from '../../core/validity.js';
import { BilatticeOrderings } from '../../qualitative/BilatticeOrderings.js';
import { QualitativeCapacity } from '../../qualitative/QualitativeCapacity.js';
import { QualitativeScale } from '../../qualitative/QualitativeScale.js';
import { UnifiedEvidenceFusion } from '../../qualitative/UnifiedEvidenceFusion.js';

/**
 * BaseRule - Standard interface for all authorization rules
 * 
 * This abstract base class defines the standard API that all rule implementations
 * must follow to ensure consistency, performance, and maintainability.
 * 
 * STANDARDIZED RESULT STRUCTURE:
 * All rules return both authorization results AND collected values:
 * {
 *   // AUTHORIZATION (core purpose)
 *   possibility_allow: number,     // 0-1, aggregated possibility across paths
 *   possibility_deny: number,      // 0-1, denial confidence  
 *   reliability: number,           // 0-1, confidence in evaluation
 *   
 *   // VALUE COLLECTION (optional metadata)
 *   collectedValues: [             // Array of individual values with paths
 *     {
 *       value: number,             // The actual value
 *       possibility: number,       // Individual confidence in this value
 *       path: string[],           // Full traversal path to this value
 *       source: {                 // Where value came from
 *         entityKey: string,
 *         relation: string, 
 *         step: number
 *       },
 *       metadata: {               // Rich metadata
 *         timestamp: number,
 *         reliability: number,
 *         decay: object
 *       }
 *     }
 *   ],
 *   
 *   // EXISTING FIELDS
 *   meta_allow: object,
 *   meta_deny: object,
 *   reason: string
 * }
 */
export class BaseRule {
  constructor(arbiter) {
    if (new.target === BaseRule) {
      throw new Error('BaseRule is abstract and cannot be instantiated directly');
    }
    
    this.arbiter = arbiter;
    this.ruleType = this.constructor.name.replace('Rule', '').toLowerCase();
    
    // Performance tracking
    this.evaluationCount = 0;
    this.totalEvaluationTime = 0;
  }

  /**
   * Standard rule evaluation interface
   * 
   * @param {number} userId - Internal user node ID
   * @param {string} userKey - User key (e.g., "user:alice")
   * @param {number} objectId - Internal object node ID  
   * @param {string} objectKey - Object key (e.g., "doc:secret")
   * @param {Object} rule - Rule configuration object
   * @param {Set} visited - Set of visited nodes for cycle detection
   * @param {string} currentRelation - Current relation being evaluated
   * @param {Object} options - Evaluation options
   * @returns {RuleEvaluationResult} Standardized result object
   */
  evaluate(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options = {}) {
    const startTime = Date.now();
    this.evaluationCount++;
    
    try {
      // Validate inputs
      const validation = this._validateInputs(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options);
      if (!validation.valid) {
        return this._createErrorResult(validation.reason, validation.details);
      }
      
      // Normalize options
      const normalizedOptions = this._normalizeOptions(options, rule);
      
      // Check for early termination conditions
      const earlyExit = this._checkEarlyExit(normalizedOptions, rule);
      if (earlyExit) {
        return earlyExit;
      }
      
      // Delegate to concrete implementation
      const result = this._evaluateRule(userId, userKey, objectId, objectKey, rule, visited, currentRelation, normalizedOptions);
      
      // Post-process result
      const finalResult = this._postProcessResult(result, rule, normalizedOptions);
      
      // Track performance
      this.totalEvaluationTime += Date.now() - startTime;
      
      return finalResult;
      
    } catch (error) {
      this.totalEvaluationTime += Date.now() - startTime;
      return this._createErrorResult('evaluation_error', { error: error.message, stack: error.stack });
    }
  }

  /**
   * Get performance statistics for this rule
   * @returns {Object} Performance metrics
   */
  getPerformanceStats() {
    return {
      ruleType: this.ruleType,
      evaluationCount: this.evaluationCount,
      totalEvaluationTime: this.totalEvaluationTime,
      averageEvaluationTime: this.evaluationCount > 0 ? this.totalEvaluationTime / this.evaluationCount : 0
    };
  }

  /**
   * Reset performance counters
   */
  resetPerformanceStats() {
    this.evaluationCount = 0;
    this.totalEvaluationTime = 0;
  }

  // ========== HELPER METHODS FOR STANDARDIZED RESULTS ==========

  /**
   * Create standardized rule result with collected values
   * @protected
   */
  _defaultValidity() {
    return DEFAULT_VALIDITY;
  }

  _validity(operator, sources = [], sourceLabels = [], K = 1, possibility = 0) {
    return buildValidity({ operator, sources, sourceLabels, K, possibility });
  }

  _relationValidity(rel) {
    return normalizeValidity(rel && rel.validity !== undefined ? rel.validity : null);
  }

  _createStandardResult(authResult, collectedValues = []) {
    const result = {
      // AUTHORIZATION
      possibility: authResult.possibility || 0,
      reliability: authResult.reliability !== undefined ? authResult.reliability : 1.0,
      validity: authResult.validity || DEFAULT_VALIDITY,
      
      // BINARY MODE FIELDS
      possibility_allow: authResult.possibility_allow !== undefined ? authResult.possibility_allow : (authResult.possibility || 0),
      possibility_deny: authResult.possibility_deny !== undefined ? authResult.possibility_deny : 0,
      
      // VALUE COLLECTION
      collectedValues: collectedValues,
      
      // EXISTING FIELDS
      meta: authResult.meta || null,
      meta_allow: authResult.meta_allow,
      meta_deny: authResult.meta_deny,
      remediation: authResult.remediation,
      reason: authResult.reason
    };
    
    return result;
  }

  /**
   * Create a collected value entry with full metadata
   * @protected
   */
  _createCollectedValue(value, possibility, path, source, metadata = {}) {
    // Timestamps honor the caller's pinned clock when present; the wall
    // clock is only the fallback for unpinned callers.
    const clockNow = (typeof metadata._now === 'number') ? metadata._now : Date.now();
    return {
      value: value,
      possibility: possibility ?? 1.0,
      path: Array.isArray(path) ? [...path] : [path],
      source: {
        ...source,
        entityKey: source.entityKey,
        relation: source.relation,
        step: source.step !== undefined ? source.step : 0
      },
      metadata: {
        timestamp: metadata.timestamp || clockNow,
        reliability: metadata.reliability !== undefined ? metadata.reliability : 1.0,
        decay: metadata.decay || null,
        ...metadata
      }
    };
  }

  /**
   * Quick reachability failure check for early termination
   * @protected
   * @param {string} sourceKey - Source entity key
   * @param {string} targetKey - Target entity key
   * @param {Object} options - Options (direction: 'forward' | 'backward')
   * @returns {boolean|null} - true if reachable, false if not reachable, null if no reachability checker
   */
  _quickReachabilityCheck(sourceKey, targetKey, options = {}) {
    if (!this.arbiter.reachabilityChecker) {
      return null; // No reachability checker available
    }
    
    try {
      // Get node IDs for PLTC query
      const sourceId = this.arbiter.resolveNodeId(sourceKey, options);
      const targetId = this.arbiter.resolveNodeId(targetKey, options);
      
      if (sourceId === undefined || targetId === undefined) {
        return false; // Nodes don't exist
      }
      
      // Use reachability checker with direction option for PLTC
      return this.arbiter.reachabilityChecker.isReachable(sourceId, targetId, options);
    } catch (error) {
      console.warn(`Reachability check failed for ${sourceKey} -> ${targetKey}:`, error);
      return null;
    }
  }

  /**
   * Quick reachability failure check with early termination
   * Returns a standardized result for unreachable cases
   * @protected
   * @param {string} sourceKey - Source entity key
   * @param {string} targetKey - Target entity key
   * @param {string} reason - Reason for the check
   * @returns {Object|null} - Standardized result if unreachable, null if reachable or no checker
   */
  _quickReachabilityFailure(sourceKey, targetKey, reason = 'Not reachable via reachability index', options = {}) {
    const isReachable = this._quickReachabilityCheck(sourceKey, targetKey, options);
    
    if (isReachable === false) {
      // Quick failure - not reachable
      return this._createStandardResult({
        possibility: 0,
        possibility_allow: 0,
        possibility_deny: 1.0,
        reliability: 1.0,
        reason: reason,
        meta: {
          method: 'reachability_quick_fail',
          sourceKey,
          targetKey,
          timestamp: Date.now()
        }
      });
    }
    
    return null; // Reachable or no checker available
  }

  /**
   * Batch reachability check for multiple source-target pairs
   * @protected
   * @param {Array} pairs - Array of {sourceKey, targetKey} objects
   * @returns {Object} - Map of reachability results
   */
  _batchReachabilityCheck(pairs) {
    if (!this.arbiter.reachabilityChecker) {
      return null;
    }
    
    const results = {};
    
    for (const {sourceKey, targetKey} of pairs) {
      try {
        results[`${sourceKey}->${targetKey}`] = this.arbiter.isReachable(sourceKey, targetKey);
      } catch (error) {
        console.warn(`Batch reachability check failed for ${sourceKey} -> ${targetKey}:`, error);
        results[`${sourceKey}->${targetKey}`] = null;
      }
    }
    
    return results;
  }

  /**
   * Get reachable nodes for a source with quick failure check
   * @protected
   * @param {string} sourceKey - Source entity key
   * @param {number} maxResults - Maximum number of results
   * @returns {Array|null} - Array of reachable node keys or null if no checker
   */
  _getReachableNodes(sourceKey, maxResults = 1000) {
    if (!this.arbiter.reachabilityChecker) {
      return null;
    }
    
    try {
      return this.arbiter.getReachableNodes(sourceKey, maxResults);
    } catch (error) {
      console.warn(`Get reachable nodes failed for ${sourceKey}:`, error);
      return null;
    }
  }

  /**
   * Get nodes that can reach a target with quick failure check
   * @protected
   * @param {string} targetKey - Target entity key
   * @param {number} maxResults - Maximum number of results
   * @returns {Array|null} - Array of reaching node keys or null if no checker
   */
  _getReachingNodes(targetKey, maxResults = 1000) {
    if (!this.arbiter.reachabilityChecker) {
      return null;
    }
    
    try {
      return this.arbiter.getReachingNodes(targetKey, maxResults);
    } catch (error) {
      console.warn(`Get reaching nodes failed for ${targetKey}:`, error);
      return null;
    }
  }

  /**
   * Aggregate collected values for consumption by other rules using OWAFusion
   * @protected
   */
  _aggregateCollectedValues(collectedValues, method = 'sum', options = {}) {
    if (!collectedValues || collectedValues.length === 0) {
      return { value: null, hasValue: false };
    }

    const values = collectedValues.map(cv => cv.value);
    const possibilities = collectedValues.map(cv => cv.possibility);
    const metas = collectedValues.map(cv => ({
      path: cv.path,
      source: cv.source,
      reliability: cv.metadata?.reliability || 1.0,
      timestamp: cv.metadata?.timestamp,
      originalValue: cv.value,
      originalPossibility: cv.possibility
    }));
    
    // Use OWAFusion for aggregation
    const valueResult = OWAFusion.fuseWithMeta(values, metas, null, method);
    const possibilityResult = OWAFusion.fuseWithMeta(possibilities, metas, null, method);
    
    return {
      value: valueResult.value,
      possibility: possibilityResult.value,
      hasValue: true,
      aggregationMeta: {
        method: method,
        sourceCount: collectedValues.length,
        sourcePaths: collectedValues.map(cv => cv.path),
        selectedSource: valueResult.meta,
        valueContribution: valueResult.value,
        possibilityContribution: possibilityResult.value,
        allValues: values,
        allPossibilities: possibilities
      }
    };
  }

  /**
   * Bilattice-enhanced evidence combination for rigorous epistemic reasoning
   * 
   * This method provides sophisticated evidence fusion using bilattice orderings
   * while maintaining compatibility with existing possibilistic infrastructure.
   * 
   * @param {Array} collectedValues - Array of collected values with metadata
   * @param {Object} options - Combination options
   * @param {string} options.method - Fusion method ('max', 'min', 'majority', etc.)
   * @param {boolean} options.useBilattice - Whether to use bilattice reasoning (default: false)
   * @param {QualitativeCapacity} options.capacity - Capacity for bilattice analysis
   * @param {QualitativeScale} options.scale - Qualitative scale for bilattice operations
   * @param {string} options.epistemicMode - 'information', 'truth', or 'hybrid' (default: 'hybrid')
   * @returns {Object} Enhanced fusion result with epistemic analysis
   * @protected
   */
  _combineEvidenceWithBilattice(collectedValues, options = {}) {
    const {
      method = 'max',
      useBilattice = false,
      capacity = null,
      scale = null,
      epistemicMode = 'hybrid',
      reconciliationMethod = 'bilattice',
      capacityType = 'simple_support',
      mode = 'qualitative'
    } = options;

    if (!useBilattice) {
      return this._aggregateCollectedValues(collectedValues, method, options);
    }

    try {
      // Use the unified evidence fusion system
      const fusionResult = UnifiedEvidenceFusion.fuse(collectedValues, {
        mode,
        aggregationMethod: method,
        reconciliationMethod: useBilattice ? reconciliationMethod : 'none',
        epistemicMode,
        capacityType,
        scale: scale || QualitativeScale.fivePoint(),
        useReconciliation: useBilattice,
        reliabilityWeighting: options.reliabilityWeighting || false
      });

      return {
        value: fusionResult.value,
        possibility: fusionResult.possibility,
        hasValue: fusionResult.hasValue,
        aggregationMeta: {
          method: fusionResult.fusionMethod,
          aggregationMethod: fusionResult.aggregationMethod,
          reconciliationMethod: fusionResult.reconciliationMethod,
          selectedSource: {
            type: 'unified_evidence_fusion',
            mode,
            epistemicMode,
            capacityType
          }
        },
        epistemicAnalysis: fusionResult.epistemicAnalysis
      };
    } catch (error) {
      console.warn('Unified evidence fusion failed, falling back to standard aggregation:', error);
      return this._aggregateCollectedValues(collectedValues, method, options);
    }
  }

  /**
   * Create a qualitative capacity from collected values for bilattice analysis
   * 
   * @param {Array} collectedValues - Array of collected values
   * @param {QualitativeScale} scale - Qualitative scale to use
   * @param {string} capacityType - Type of capacity ('simple_support', 'possibility', 'necessity')
   * @returns {QualitativeCapacity} Capacity for bilattice analysis
   * @protected
   */
  _createCapacityFromValues(collectedValues, scale, capacityType = 'simple_support') {
    if (!collectedValues || collectedValues.length === 0) {
      return null;
    }

    // Create state space from unique values
    const uniqueValues = [...new Set(collectedValues.map(cv => cv.value))];
    const stateSpace = uniqueValues.map((_, index) => `evidence_${index}`);

    switch (capacityType) {
      case 'simple_support':
        // Create simple support capacity for each piece of evidence
        const qmt = new Map();
        collectedValues.forEach((cv, index) => {
          const evidenceSet = new Set([`evidence_${index}`]);
          qmt.set(evidenceSet, cv.possibility);
        });
        return new QualitativeCapacity(stateSpace, scale, qmt);

      case 'possibility':
        // Create possibility measure (all focal sets are singletons)
        const possibilityQmt = new Map();
        collectedValues.forEach((cv, index) => {
          const singletonSet = new Set([`evidence_${index}`]);
          possibilityQmt.set(singletonSet, cv.possibility);
        });
        return new QualitativeCapacity(stateSpace, scale, possibilityQmt);

      case 'necessity':
        // Create necessity measure (nested focal sets)
        const necessityQmt = new Map();
        const sortedValues = collectedValues
          .map((cv, index) => ({ value: cv.value, possibility: cv.possibility, index }))
          .sort((a, b) => b.possibility - a.possibility);

        sortedValues.forEach((item, rank) => {
          const nestedSet = new Set(sortedValues.slice(0, rank + 1).map(sv => `evidence_${sv.index}`));
          necessityQmt.set(nestedSet, item.possibility);
        });
        return new QualitativeCapacity(stateSpace, scale, necessityQmt);

      default:
        throw new Error(`Unknown capacity type: ${capacityType}`);
    }
  }

  // ========== PROTECTED METHODS (to be implemented by subclasses) ==========

  /**
   * Concrete rule evaluation implementation
   * @protected
   */
  _evaluateRule(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options) {
    throw new Error(`${this.constructor.name} must implement _evaluateRule()`);
  }

  // ========== PRIVATE HELPER METHODS ==========

  /**
   * Validate input parameters
   * @private
   */
  _validateInputs(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options) {
    // Check for required parameters
    if (userId === undefined || userId === null) {
      return { valid: false, reason: 'missing_user_id', details: { userKey } };
    }
    
    if (objectId === undefined || objectId === null) {
      return { valid: false, reason: 'missing_object_id', details: { objectKey } };
    }
    
    if (!userKey || typeof userKey !== 'string') {
      return { valid: false, reason: 'invalid_user_key', details: { userKey } };
    }
    
    if (!objectKey || typeof objectKey !== 'string') {
      return { valid: false, reason: 'invalid_object_key', details: { objectKey } };
    }
    
    if (!rule || typeof rule !== 'object') {
      return { valid: false, reason: 'invalid_rule', details: { rule } };
    }
    
    if (!visited || typeof visited.has !== 'function') {
      return { valid: false, reason: 'invalid_visited_set', details: { visited } };
    }
    
    return { valid: true };
  }

  /**
   * Normalize and validate options
   * @private
   */
  _normalizeOptions(options, rule) {
    const normalized = {
      // Performance options
      fastPath: options.fastPath || false,
      minPossibility: options.minPossibility || null,
      maxPossibility: options.maxPossibility || null,
      
      // Inference options
      noInfer: options.noInfer || options.no_infer || rule.no_infer || false,
      allowInference: options.allowInference !== false && !options.noInfer && !options.no_infer && !rule.no_infer,
      
      // Binary mode
      binary: options.binary || false,
      
      // Evaluation tracking
      trackEvaluation: options.trackEvaluation !== false,
      
      // Rule-specific options (pass through)
      ...options
    };
    
    // Validate threshold values
    if (normalized.minPossibility !== null) {
      normalized.minPossibility = Math.max(0, Math.min(1, normalized.minPossibility));
    }
    
    if (normalized.maxPossibility !== null) {
      normalized.maxPossibility = Math.max(0, Math.min(1, normalized.maxPossibility));
    }
    
    return normalized;
  }

  /**
   * Check for early exit conditions
   * @private
   */
  _checkEarlyExit(options, rule) {
    // Cycle detection is handled at a higher level
    // Rule-specific early exits should be implemented in _evaluateRule
    return null;
  }

  /**
   * Post-process evaluation result
   * @private
   */
  _postProcessResult(result, rule, options) {
    const includeMeta = options.includeMeta !== undefined ? options.includeMeta : true;
    const includeValues = options.collectValues !== undefined ? options.collectValues : true;
    const { meta, meta_allow, meta_deny, collectedValues, ...rest } = result;

    // Ensure result has required fields with standardized structure
    const processed = {
      // AUTHORIZATION
      possibility: result.possibility || 0,
      reliability: result.reliability !== undefined ? result.reliability : 1.0,

      // EXISTING FIELDS
      reason: result.reason,

      // PRESERVE OTHER FIELDS (for backward compatibility)
      ...rest
    };

    if (includeValues) {
      processed.collectedValues = collectedValues || [];
    }

    if (includeMeta) {
      processed.meta = meta || null;
      if (meta_allow !== undefined) {
        processed.meta_allow = meta_allow;
      }
      if (meta_deny !== undefined) {
        processed.meta_deny = meta_deny;
      }
    }
    
    // Add evaluation metadata if tracking is enabled
    if (options.trackEvaluation && includeMeta && result.evaluation) {
      processed.evaluation = {
        ruleType: this.ruleType,
        evaluationTime: Date.now() - (result.evaluation.evaluationStarted || Date.now()),
        ...result.evaluation
      };
    }
    
    // Apply early exit metadata if applicable
    if (options.fastPath && includeMeta && this._shouldMarkEarlyExit(processed, options)) {
      if (processed.meta) {
        processed.meta.earlyExit = true;
        processed.meta.earlyExitReason = this._getEarlyExitReason(processed, options);
      }
    }
    
    return processed;
  }

  /**
   * Create standardized error result
   * @private
   */
  _createErrorResult(reason, details = {}) {
    return {
      possibility_allow: 0,
      possibility_deny: 0,
      reliability: 1.0,
      meta_allow: null,
      meta_deny: null,
      reason,
      error: true,
      details
    };
  }

  /**
   * Check if result should be marked as early exit
   * @private
   */
  _shouldMarkEarlyExit(result, options) {
    if (options.minAllowPossibility !== null && result.possibility_allow >= options.minAllowPossibility) {
      return true;
    }
    
    if (options.maxDenyPossibility !== null && result.possibility_deny >= options.maxDenyPossibility) {
      return true;
    }
    
    return false;
  }

  /**
   * Get early exit reason
   * @private
   */
  _getEarlyExitReason(result, options) {
    if (options.minAllowPossibility !== null && result.possibility_allow >= options.minAllowPossibility) {
      return 'allow_threshold_met';
    }
    
    if (options.maxDenyPossibility !== null && result.possibility_deny >= options.maxDenyPossibility) {
      return 'deny_threshold_met';
    }
    
    return 'unknown';
  }
}

/**
 * Standard result structure for rule evaluations
 * @typedef {Object} RuleEvaluationResult
 * @property {number} possibility_allow - Possibility of allowing access (0-1)
 * @property {number} possibility_deny - Possibility of denying access (0-1)
 * @property {number} reliability - Reliability of the evaluation (0-1)
 * @property {Object|null} meta_allow - Metadata for allow decision
 * @property {Object|null} meta_deny - Metadata for deny decision
 * @property {string} reason - Reason for the result (optional)
 * @property {boolean} error - Whether this is an error result (optional)
 * @property {Object} details - Additional details (optional)
 * @property {Object} evaluation - Evaluation tracking data (optional)
 */ 
