// Rule Registry - Centralized rule loading, caching, and pattern compilation
// Loads rules from JSON files and compiles regex patterns for performance
// Supports rule versioning, validation, and server-based rule updates
// Supports v2.0.0 enhanced rules via EnhancedRuleEngine integration

import { EnhancedRuleEngine } from './enhanced-rule-engine.js';

export class RuleRegistry {
  constructor(config = {}) {
    this.config = config;
    this.rules = new Map();              // "category/subcategory" -> rules object
    this.metadata = new Map();            // rule_id -> { version, date, source, checksum }
    this.compiledPatterns = new Map();    // pattern_string -> compiled_regex
    this.validators = new Map();          // category -> validator_function
    this.lastUpdate = new Map();          // category -> timestamp
    this.ruleVersion = '1.0.0';

    // v2.0.0 enhanced rule engine
    this.enhancedEngine = new EnhancedRuleEngine();
    this.enhancedRulesets = new Map();   // rulesetId -> compiled ruleset
  }

  /**
   * Load rules from a rules object (from JSON)
   * @param {string} category - Rule category (url, resource, permission, content, etc.)
   * @param {string} subcategory - Rule subcategory (domain-patterns, extensions, etc.)
   * @param {object} rulesData - Rules data object
   */
  async loadRules(category, subcategory, rulesData) {
    try {
      // Route v2.0.0 and v3.0.0 rules to EnhancedRuleEngine
      if (rulesData.schemaVersion === '2.0.0' || rulesData.schemaVersion === '3.0.0') {
        return this.loadEnhancedRules(rulesData);
      }

      // 1. Validate rule format
      this.validateRules(category, rulesData);

      // 2. Compile regex patterns (once for performance)
      const compiled = await this.compilePatterns(rulesData);

      // 3. Store with metadata
      const key = `${category}/${subcategory}`;
      this.rules.set(key, {
        ...compiled,
        category,
        subcategory,
        loaded: Date.now(),
        version: rulesData.version || '1.0.0'
      });

      // 4. Store metadata
      if (rulesData.patterns) {
        for (const pattern of rulesData.patterns) {
          if (pattern.id) {
            this.metadata.set(pattern.id, {
              version: pattern.version || rulesData.version || '1.0.0',
              category,
              subcategory,
              date: Date.now(),
              source: 'local'
            });
          }
        }
      }

      // 5. Update last update timestamp
      this.lastUpdate.set(category, Date.now());

      console.log(`[RuleRegistry] Loaded rules: ${key} (${rulesData.patterns?.length || 0} patterns)`);
      return true;
    } catch (error) {
      console.error(`[RuleRegistry] Failed to load rules for ${category}/${subcategory}:`, error);
      return false;
    }
  }

  /**
   * Load rules from JSON file
   * @param {string} category - Rule category
   * @param {string} subcategory - Rule subcategory
   * @param {string} filePath - Path to JSON file
   */
  async loadRulesFromFile(category, subcategory, filePath) {
    try {
      const response = await fetch(chrome.runtime.getURL(filePath));
      const rulesData = await response.json();
      return await this.loadRules(category, subcategory, rulesData);
    } catch (error) {
      console.error(`[RuleRegistry] Failed to load rules from file ${filePath}:`, error);
      return false;
    }
  }

  /**
   * Load all rules from a directory structure
   * @param {object} ruleManifest - Manifest of rule files { category: { subcategory: filePath } }
   */
  async loadAllRules(ruleManifest = {}) {
    // If no manifest provided, skip rule loading (backward compatibility)
    if (!ruleManifest || Object.keys(ruleManifest).length === 0) {
      console.log('[RuleRegistry] No rule manifest provided, skipping rule loading');
      return;
    }

    const promises = [];

    for (const [category, subcategories] of Object.entries(ruleManifest)) {
      if (!subcategories || typeof subcategories !== 'object') continue;

      for (const [subcategory, filePath] of Object.entries(subcategories)) {
        promises.push(this.loadRulesFromFile(category, subcategory, filePath));
      }
    }

    if (promises.length === 0) {
      console.log('[RuleRegistry] No rules to load');
      return;
    }

    const results = await Promise.all(promises);
    const successCount = results.filter(r => r === true).length;
    console.log(`[RuleRegistry] Loaded ${successCount}/${results.length} rule files`);
  }

  /**
   * Load rules from server (enterprise feature)
   * @param {string} serverUrl - Server URL for rule updates
   * @param {string} category - Optional category filter
   */
  async loadFromServer(serverUrl, category = null) {
    try {
      const url = category
        ? `${serverUrl}/api/rules/${category}`
        : `${serverUrl}/api/rules`;

      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'X-Extension-Version': chrome.runtime.getManifest().version
        }
      });

      if (!response.ok) {
        throw new Error(`Server returned ${response.status}: ${response.statusText}`);
      }

      const data = await response.json();

      // Verify signature if provided (enterprise security)
      if (data.signature && this.config.verifySignatures) {
        const isValid = await this.verifySignature(data.rules, data.signature);
        if (!isValid) {
          throw new Error('Rule signature verification failed');
        }
      }

      // Load rules
      for (const ruleSet of data.rules) {
        await this.loadRules(
          ruleSet.category,
          ruleSet.subcategory,
          ruleSet.data
        );

        // Mark as server-sourced
        this.metadata.set(`${ruleSet.category}/${ruleSet.subcategory}`, {
          source: 'server',
          serverUrl,
          version: ruleSet.version,
          date: Date.now()
        });
      }

      console.log(`[RuleRegistry] Loaded ${data.rules.length} rule sets from server`);
      return true;
    } catch (error) {
      console.error('[RuleRegistry] Failed to load rules from server:', error);
      return false;
    }
  }

  /**
   * Get rules for a category/subcategory
   * @param {string} category - Rule category
   * @param {string} subcategory - Rule subcategory (optional)
   * @returns {object} - Rules object or null
   */
  getRules(category, subcategory = null) {
    if (subcategory) {
      const key = `${category}/${subcategory}`;
      return this.rules.get(key) || null;
    }

    // Return all rules for category
    const categoryRules = {};
    for (const [key, rules] of this.rules) {
      if (key.startsWith(`${category}/`)) {
        const subcat = key.split('/')[1];
        categoryRules[subcat] = rules;
      }
    }

    return Object.keys(categoryRules).length > 0 ? categoryRules : null;
  }

  /**
   * Compile regex patterns from rule data
   * @param {object} rulesData - Rules data with patterns
   * @returns {object} - Compiled rules object
   */
  async compilePatterns(rulesData) {
    const compiled = { ...rulesData };

    // Compile pattern arrays
    if (rulesData.patterns && Array.isArray(rulesData.patterns)) {
      compiled.patterns = rulesData.patterns.map(pattern => {
        if (pattern.pattern && typeof pattern.pattern === 'string') {
          // Check if already compiled
          let regex = this.compiledPatterns.get(pattern.pattern);

          if (!regex) {
            try {
              // Extract flags if present (e.g., "/pattern/gi")
              const match = pattern.pattern.match(/^\/(.+)\/([gimuy]*)$/);
              if (match) {
                regex = new RegExp(match[1], match[2]);
              } else {
                regex = new RegExp(pattern.pattern, 'i'); // Default case-insensitive
              }
              this.compiledPatterns.set(pattern.pattern, regex);
            } catch (error) {
              console.error(`[RuleRegistry] Failed to compile pattern: ${pattern.pattern}`, error);
              regex = null;
            }
          }

          return {
            ...pattern,
            regex // Add compiled regex
          };
        }
        return pattern;
      });
    }

    // Compile other pattern fields (domainPatterns, urlPatterns, etc.)
    for (const key of Object.keys(compiled)) {
      if (key.endsWith('Patterns') && Array.isArray(compiled[key])) {
        compiled[key] = compiled[key].map(pattern => {
          if (typeof pattern === 'string') {
            let regex = this.compiledPatterns.get(pattern);
            if (!regex) {
              try {
                regex = new RegExp(pattern, 'i');
                this.compiledPatterns.set(pattern, regex);
              } catch (error) {
                console.error(`[RuleRegistry] Failed to compile pattern: ${pattern}`, error);
                return pattern;
              }
            }
            return regex;
          }
          return pattern;
        });
      }
    }

    return compiled;
  }

  /**
   * Validate rules against schema
   * @param {string} category - Rule category
   * @param {object} rulesData - Rules data to validate
   * @throws {Error} - If validation fails
   */
  validateRules(category, rulesData) {
    // Basic validation
    if (!rulesData || typeof rulesData !== 'object') {
      throw new Error('Rules data must be an object');
    }

    // Check for required fields
    if (!rulesData.version) {
      console.warn(`[RuleRegistry] Rules missing version field for category ${category}`);
    }

    // Custom validator for category
    const validator = this.validators.get(category);
    if (validator) {
      const isValid = validator(rulesData);
      if (!isValid) {
        throw new Error(`Validation failed for category ${category}`);
      }
    }

    return true;
  }

  /**
   * Register custom validator for a category
   * @param {string} category - Rule category
   * @param {function} validator - Validator function (rules) => boolean
   */
  registerValidator(category, validator) {
    this.validators.set(category, validator);
    console.log(`[RuleRegistry] Registered validator for category: ${category}`);
  }

  /**
   * Verify cryptographic signature (enterprise feature)
   * @param {object} rules - Rules data
   * @param {string} signature - Signature to verify
   * @returns {boolean} - True if valid
   */
  async verifySignature(rules, signature) {
    // Placeholder for signature verification
    // In production, implement proper crypto signature verification
    console.warn('[RuleRegistry] Signature verification not implemented');
    return true;
  }

  /**
   * Get rule metadata
   * @param {string} ruleId - Rule ID
   * @returns {object} - Metadata or null
   */
  getMetadata(ruleId) {
    return this.metadata.get(ruleId) || null;
  }

  /**
   * Get all rule metadata
   * @returns {array} - Array of metadata objects
   */
  getAllMetadata() {
    return Array.from(this.metadata.entries()).map(([id, meta]) => ({
      id,
      ...meta
    }));
  }

  /**
   * Check if rules need update
   * @param {string} category - Rule category
   * @param {number} maxAge - Max age in milliseconds
   * @returns {boolean} - True if update needed
   */
  needsUpdate(category, maxAge = 86400000) { // Default 24 hours
    const lastUpdate = this.lastUpdate.get(category);
    if (!lastUpdate) return true;
    return (Date.now() - lastUpdate) > maxAge;
  }

  /**
   * Clear all rules
   */
  clearAll() {
    this.rules.clear();
    this.metadata.clear();
    this.compiledPatterns.clear();
    this.lastUpdate.clear();
    console.log('[RuleRegistry] All rules cleared');
  }

  /**
   * Get registry statistics
   * @returns {object} - Statistics
   */
  getStats() {
    return {
      totalRuleSets: this.rules.size,
      totalPatterns: this.compiledPatterns.size,
      categories: this.getCategoryCount(),
      oldestUpdate: this.getOldestUpdate(),
      newestUpdate: this.getNewestUpdate()
    };
  }

  /**
   * Get category count
   * @returns {object} - Category counts
   */
  getCategoryCount() {
    const counts = {};
    for (const key of this.rules.keys()) {
      const category = key.split('/')[0];
      counts[category] = (counts[category] || 0) + 1;
    }
    return counts;
  }

  /**
   * Get oldest update timestamp
   * @returns {number} - Timestamp or null
   */
  getOldestUpdate() {
    const timestamps = Array.from(this.lastUpdate.values());
    return timestamps.length > 0 ? Math.min(...timestamps) : null;
  }

  /**
   * Get newest update timestamp
   * @returns {number} - Timestamp or null
   */
  getNewestUpdate() {
    const timestamps = Array.from(this.lastUpdate.values());
    return timestamps.length > 0 ? Math.max(...timestamps) : null;
  }

  // ─── v2.0.0 Enhanced Rule Methods ──────────────────────

  /**
   * Load and compile a v2.0.0 enhanced ruleset
   * @param {object} rulesData - Raw v2.0.0 ruleset JSON
   * @returns {boolean} - True if loaded successfully
   */
  loadEnhancedRules(rulesData) {
    try {
      const compiled = this.enhancedEngine.compileRuleset(rulesData);
      this.enhancedRulesets.set(compiled.rulesetId, compiled);

      // Track metadata
      this.metadata.set(compiled.rulesetId, {
        version: compiled.schemaVersion,
        schemaVersion: compiled.schemaVersion,
        category: compiled.category,
        subcategory: compiled.subcategory,
        date: Date.now(),
        source: 'enhanced',
        ruleCount: compiled.rules.length,
      });

      const key = `${compiled.category}/${compiled.subcategory || compiled.rulesetId}`;
      this.lastUpdate.set(key, Date.now());

      console.log(`[RuleRegistry] Loaded enhanced ruleset: ${compiled.rulesetId} (${compiled.rules.length} rules)`);
      return true;
    } catch (error) {
      console.error(`[RuleRegistry] Failed to load enhanced ruleset:`, error);
      return false;
    }
  }

  /**
   * Get all compiled enhanced rulesets for evaluation
   * @returns {array} Array of compiled v2.0.0 rulesets
   */
  getEnhancedRulesets() {
    return Array.from(this.enhancedRulesets.values());
  }

  /**
   * Get the EnhancedRuleEngine instance
   * @returns {EnhancedRuleEngine}
   */
  getEnhancedEngine() {
    return this.enhancedEngine;
  }
}
