// Analyzer Engine - Core risk scoring and detection orchestration
// Replaces monolithic analyzeUrl/analyzeResource/checkPermissions functions
// Supports pluggable detector modules with parallel execution

// Note: DetectorLoader import removed - dynamic imports not allowed in service workers (Manifest V3)
import { DynamicRuleDetector } from './dynamic-rule-detector.js';

class LRUCache {
  constructor(maxSize = 10000) {
    this.maxSize = maxSize;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) return null;
    // Move to end to mark as recently used
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.maxSize) {
      // Remove oldest (first) entry
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }

  clear() {
    this.cache.clear();
  }

  get size() {
    return this.cache.size;
  }
}

export class AnalyzerEngine {
  constructor(ruleRegistry, config = {}) {
    this.ruleRegistry = ruleRegistry;
    this.config = config;
    this.detectors = new Map();              // detector_name -> detector_instance
    this.detectorsByCategory = new Map();    // category -> [detector_names]
    this.cache = new LRUCache(config.cacheSize || 10000);
    this.performanceMetrics = {
      totalAnalyses: 0,
      cacheHits: 0,
      avgLatency: 0,
      detectorLatencies: {}
    };
    this.enabled = true;

    // v3: Signal-based architecture
    this.featureStore = null;
    this.signalProviderRegistry = null;
  }

  /**
   * Set the FeatureStore for v3 signal-based detection.
   * @param {object} store - FeatureStore instance
   */
  setFeatureStore(store) {
    this.featureStore = store;
    // Propagate to DynamicRuleDetector if already registered
    const dynDetector = this.detectors.get('dynamic-rule-v2');
    if (dynDetector && dynDetector.setFeatureStore) {
      dynDetector.setFeatureStore(store);
    }
  }

  /**
   * Set the SignalProviderRegistry for v3 signal-based detection.
   * @param {object} registry - SignalProviderRegistry instance
   */
  setProviderRegistry(registry) {
    this.signalProviderRegistry = registry;
    // Propagate to DynamicRuleDetector if already registered
    const dynDetector = this.detectors.get('dynamic-rule-v2');
    if (dynDetector && dynDetector.setProviderRegistry) {
      dynDetector.setProviderRegistry(registry);
    }
  }

  /**
   * Register a detector module
   * @param {string} name - Detector name
   * @param {object} detector - Detector instance implementing detect() method
   */
  registerDetector(name, detector) {
    if (!detector.detect || typeof detector.detect !== 'function') {
      throw new Error(`Detector ${name} must implement detect() method`);
    }

    this.detectors.set(name, detector);

    // Organize by category
    const category = detector.category || 'general';
    if (!this.detectorsByCategory.has(category)) {
      this.detectorsByCategory.set(category, []);
    }
    this.detectorsByCategory.get(category).push(name);

    console.log(`[AnalyzerEngine] Registered detector: ${name} (category: ${category})`);
  }

  /**
   * Load and register all detectors for a category
   * @param {string} category - Detector category (url, resource, permission, etc.)
   * @param {array} detectorModules - Array of detector instances
   */
  async loadDetectors(category, detectorModules) {
    for (const module of detectorModules) {
      if (module.name && module.detect) {
        this.registerDetector(module.name, module);
      }
    }
  }

  /**
   * Main analysis function - analyzes target using specified detectors
   * @param {object} target - Analysis target { url, type, resource, permissions, etc. }
   * @param {array} detectorNames - Array of detector names to run, or 'all'
   * @param {object} options - Analysis options { bypassCache, earlyExit, maxLatency }
   * @returns {object} - { riskScore, findings, detections, riskLevel, metadata }
   */
  async analyze(target, detectorNames = 'all', options = {}) {
    if (!this.enabled) {
      return { riskScore: 0, findings: [], detections: [], riskLevel: 'none', cached: false };
    }

    const startTime = performance.now();
    const cacheKey = this.getCacheKey(target, detectorNames);

    // Check cache unless bypassed
    if (!options.bypassCache) {
      const cached = this.cache.get(cacheKey);
      if (cached) {
        this.performanceMetrics.cacheHits++;
        return { ...cached, cached: true };
      }
    }

    // Determine which detectors to run
    const detectorsToRun = this.getDetectorsToRun(detectorNames, target.type);

    if (detectorsToRun.length === 0) {
      console.warn(`[AnalyzerEngine] No detectors found for ${JSON.stringify(detectorNames)}`);
      return { riskScore: 0, findings: [], detections: [], riskLevel: 'none', cached: false };
    }

    // Run detectors in parallel
    const detectionPromises = detectorsToRun.map(async (detectorName) => {
      const detector = this.detectors.get(detectorName);
      if (!detector || detector.enabled === false) {
        return null;
      }

      const detectorStart = performance.now();
      try {
        const result = await detector.detect(target, this.ruleRegistry);
        const latency = performance.now() - detectorStart;

        // Track performance
        if (!this.performanceMetrics.detectorLatencies[detectorName]) {
          this.performanceMetrics.detectorLatencies[detectorName] = [];
        }
        this.performanceMetrics.detectorLatencies[detectorName].push(latency);

        return {
          ...result,
          detectorName,
          latency
        };
      } catch (error) {
        console.error(`[AnalyzerEngine] Detector ${detectorName} failed:`, error);
        return null;
      }
    });

    // Wait for all detectors to complete (with timeout if specified)
    let results;
    if (options.maxLatency) {
      const timeoutPromise = new Promise((resolve) =>
        setTimeout(() => resolve([]), options.maxLatency)
      );
      results = await Promise.race([
        Promise.all(detectionPromises),
        timeoutPromise
      ]);
    } else {
      results = await Promise.all(detectionPromises);
    }

    // Filter out null results (failed detectors)
    const validResults = results.filter(r => r !== null);

    // Aggregate findings
    const aggregated = this.aggregateFindings(validResults);

    // Calculate composite risk score
    const riskScore = this.calculateRiskScore(aggregated);

    // Determine risk level
    const riskLevel = this.getRiskLevel(riskScore);

    // Build final analysis result
    const analysis = {
      riskScore,
      findings: aggregated,
      detections: validResults,
      riskLevel,
      metadata: {
        detectorsRun: detectorsToRun.length,
        detectorsMatched: validResults.filter(r => r.matched).length,
        targetType: target.type,
        timestamp: Date.now(),
        latency: performance.now() - startTime
      },
      cached: false
    };

    // Cache result
    this.cache.set(cacheKey, analysis);

    // Update metrics
    this.performanceMetrics.totalAnalyses++;
    this.performanceMetrics.avgLatency =
      ((this.performanceMetrics.avgLatency * (this.performanceMetrics.totalAnalyses - 1)) +
       analysis.metadata.latency) / this.performanceMetrics.totalAnalyses;

    // Early exit if high risk and option enabled
    if (options.earlyExit && riskLevel === 'high') {
      console.log(`[AnalyzerEngine] Early exit triggered for high-risk detection`);
    }

    return analysis;
  }

  /**
   * Aggregate findings from multiple detector results
   * @param {array} results - Array of detector results
   * @returns {array} - Aggregated findings
   */
  aggregateFindings(results) {
    const findings = [];

    for (const result of results) {
      if (result.matched && result.findings) {
        findings.push(...result.findings.map(f => ({
          ...f,
          detectorName: result.detectorName
        })));
      }
    }

    return findings;
  }

  /**
   * Calculate composite risk score from findings
   * @param {array} findings - Array of findings
   * @returns {number} - Risk score
   */
  calculateRiskScore(findings) {
    if (findings.length === 0) return 0;

    // Sum weighted scores
    let totalScore = 0;
    for (const finding of findings) {
      const weight = finding.weight || 1;
      const severity = this.getSeverityMultiplier(finding.severity);
      totalScore += weight * severity;
    }

    return totalScore;
  }

  /**
   * Get severity multiplier for scoring
   * @param {string} severity - Severity level (critical, high, medium, low)
   * @returns {number} - Multiplier
   */
  getSeverityMultiplier(severity) {
    const multipliers = {
      critical: 4,
      high: 3,
      medium: 2,
      low: 1
    };
    return multipliers[severity] || 1;
  }

  /**
   * Determine risk level from score
   * @param {number} riskScore - Calculated risk score
   * @returns {string} - Risk level (critical, high, medium, low, none)
   */
  getRiskLevel(riskScore) {
    const thresholds = this.config.thresholds || {
      critical: 10,
      high: 7,
      medium: 4,
      low: 2
    };

    if (riskScore >= thresholds.critical) return 'critical';
    if (riskScore >= thresholds.high) return 'high';
    if (riskScore >= thresholds.medium) return 'medium';
    if (riskScore >= thresholds.low) return 'low';
    return 'none';
  }

  /**
   * Get detectors to run based on specification
   * @param {array|string} detectorNames - Detector names or 'all'
   * @param {string} targetType - Type of target being analyzed
   * @returns {array} - Array of detector names to run
   */
  getDetectorsToRun(detectorNames, targetType) {
    if (detectorNames === 'all') {
      return Array.from(this.detectors.keys());
    }

    if (Array.isArray(detectorNames)) {
      return detectorNames.filter(name => this.detectors.has(name));
    }

    // Get by category
    if (this.detectorsByCategory.has(detectorNames)) {
      return this.detectorsByCategory.get(detectorNames);
    }

    return [];
  }

  /**
   * Generate cache key for analysis
   * @param {object} target - Analysis target
   * @param {array} detectorNames - Detector names
   * @returns {string} - Cache key
   */
  getCacheKey(target, detectorNames) {
    const targetKey = target.url || target.id || JSON.stringify(target);
    const detectorsKey = Array.isArray(detectorNames) ? detectorNames.join(',') : detectorNames;
    return `${targetKey}|${detectorsKey}`;
  }

  /**
   * Get performance metrics
   * @returns {object} - Performance metrics
   */
  getMetrics() {
    return {
      ...this.performanceMetrics,
      cacheSize: this.cache.size,
      cacheHitRate: this.performanceMetrics.totalAnalyses > 0
        ? this.performanceMetrics.cacheHits / this.performanceMetrics.totalAnalyses
        : 0,
      detectorsRegistered: this.detectors.size
    };
  }

  /**
   * Clear cache
   */
  clearCache() {
    this.cache.clear();
    console.log('[AnalyzerEngine] Cache cleared');
  }

  /**
   * Enable/disable engine
   * @param {boolean} enabled - Enable state
   */
  setEnabled(enabled) {
    this.enabled = enabled;
    console.log(`[AnalyzerEngine] Engine ${enabled ? 'enabled' : 'disabled'}`);
  }

  /**
   * Get detector information
   * @returns {array} - Array of detector info objects
   */
  getDetectorInfo() {
    const info = [];
    for (const [name, detector] of this.detectors) {
      info.push({
        name,
        category: detector.category || 'general',
        version: detector.version || '1.0.0',
        enabled: detector.enabled !== false,
        description: detector.description || ''
      });
    }
    return info;
  }

  /**
   * Get total detector count
   * @returns {number} - Number of registered detectors
   */
  getDetectorCount() {
    return this.detectors.size;
  }

  /**
   * Initialize engine and load all detectors
   */
  async initialize() {
    console.log('[AnalyzerEngine] Initializing...');

    // Register the v2/v3 dynamic rule detector
    if (this.ruleRegistry && this.ruleRegistry.getEnhancedEngine) {
      const enhancedEngine = this.ruleRegistry.getEnhancedEngine();

      // Wire v3 dependencies into the enhanced engine
      if (this.signalProviderRegistry) {
        enhancedEngine.setProviderRegistry(this.signalProviderRegistry);
      }
      if (this.featureStore) {
        enhancedEngine.setFeatureStore(this.featureStore);
      }

      const dynamicDetector = new DynamicRuleDetector(enhancedEngine, this.ruleRegistry, {
        featureStore: this.featureStore,
        signalProviderRegistry: this.signalProviderRegistry,
      });
      this.registerDetector('dynamic-rule-v2', dynamicDetector);
    }

    // Note: Additional detector loading disabled for now (requires detectors directory to be populated)
    // Dynamic imports are not allowed in service workers (Manifest V3)

    console.log(`[AnalyzerEngine] Initialization complete - ${this.getDetectorCount()} detectors registered`);
  }

  /**
   * Convenience method: Analyze URL
   * @param {string} url - URL to analyze
   * @param {object} [context] - Optional context { tabId, frameId }
   * @returns {object} - Analysis result with matched flag
   */
  async analyzeUrl(url, context = {}) {
    const target = { url, type: 'url' };
    if (context.tabId !== undefined) target._tabId = context.tabId;
    if (context.frameId !== undefined) target._frameId = context.frameId;

    const result = await this.analyze(
      target,
      ['url-detectors', 'social-engineering'],
      { earlyExit: true }
    );

    return {
      ...result,
      matched: result.findings.length > 0
    };
  }

  /**
   * Convenience method: Analyze resource
   * @param {object} details - Resource details from webRequest
   * @returns {object} - Analysis result with matched flag
   */
  async analyzeResource(details) {
    const target = {
      url: details.url,
      type: 'resource',
      resourceType: details.type,
      details
    };
    if (details.tabId !== undefined) target._tabId = details.tabId;
    if (details.frameId !== undefined) target._frameId = details.frameId;

    const result = await this.analyze(
      target,
      ['resource-detectors', 'code-injection'],
      { earlyExit: true }
    );

    return {
      ...result,
      matched: result.findings.length > 0
    };
  }

  /**
   * Convenience method: Analyze permissions
   * @param {object} permissions - Permissions object
   * @returns {object} - Analysis result with matched flag
   */
  async analyzePermissions(permissions) {
    const result = await this.analyze(
      {
        permissions,
        type: 'permissions'
      },
      ['permission-detectors', 'malicious-extensions'],
      {}
    );

    return {
      ...result,
      matched: result.findings.length > 0
    };
  }

  /**
   * Get analysis result summary
   * @param {object} analysis - Analysis result
   * @returns {object} - Summary with matched flag
   */
  getSummary(analysis) {
    return {
      matched: analysis.findings.length > 0,
      riskScore: analysis.riskScore,
      riskLevel: analysis.riskLevel,
      findingCount: analysis.findings.length,
      findings: analysis.findings
    };
  }
}
