// Dynamic Rule Updater
// Fetches detection rules from API server every 6 hours
// Implements checksum verification, rollback, and update scheduling

import { API_ENDPOINTS } from './api-endpoints.js';

export class DynamicRuleUpdater {
  constructor(ruleRegistry, config = {}) {
    this.ruleRegistry = ruleRegistry;
    this.config = config;

    // API configuration with fallback domains
    this.apiUrl = config.ruleApiUrl || 'https://api.nithic.ai';
    this.fallbackUrls = API_ENDPOINTS.ruleApi;
    this.updateInterval = config.updateIntervalHours || 6;
    this.maxRetries = config.maxRetries || 3;

    // State
    this.currentVersion = null;
    this.lastUpdateTime = null;
    this.updateTimer = null;
    this.isUpdating = false;
    this.ruleHistory = []; // Keep last 5 versions for rollback
    this.maxHistorySize = 5;

    // Statistics
    this.stats = {
      totalUpdates: 0,
      successfulUpdates: 0,
      failedUpdates: 0,
      lastError: null,
      rulesApplied: 0
    };
  }

  /**
   * Initialize updater and check for updates
   */
  async initialize() {
    console.log('[DynamicRuleUpdater] Initializing...');

    try {
      // Load current version from storage
      const stored = await chrome.storage.local.get(['dynamicRules', 'rulesVersion', 'lastRuleUpdate']);

      if (stored.dynamicRules && stored.rulesVersion) {
        this.currentVersion = stored.rulesVersion;
        this.lastUpdateTime = stored.lastRuleUpdate;

        // Apply stored rules to registry
        await this.applyRulesToRegistry(stored.dynamicRules);

        console.log(`[DynamicRuleUpdater] Loaded stored rules v${this.currentVersion}`);
      }

      // Check for updates immediately
      await this.checkForUpdates();

      // Schedule periodic updates
      this.scheduleNextUpdate();

      console.log(`[DynamicRuleUpdater] Initialized (update interval: ${this.updateInterval}h)`);
      return true;

    } catch (error) {
      console.error('[DynamicRuleUpdater] Initialization failed:', error);
      this.stats.lastError = error.message;
      return false;
    }
  }

  /**
   * Check for rule updates from API server
   */
  async checkForUpdates() {
    if (this.isUpdating) {
      console.log('[DynamicRuleUpdater] Update already in progress, skipping');
      return;
    }

    this.isUpdating = true;
    this.stats.totalUpdates++;

    try {
      console.log('[DynamicRuleUpdater] Checking for updates...');

      // Fetch latest rules from API (with domain fallback)
      const urls = [this.apiUrl, ...this.fallbackUrls.filter(u => u !== this.apiUrl)];
      let response;
      let lastErr;
      for (const baseUrl of urls) {
        try {
          response = await fetch(`${baseUrl}/v1/rules/latest`, {
            method: 'GET',
            headers: {
              'Content-Type': 'application/json',
              'X-Extension-Version': chrome.runtime.getManifest().version,
              'X-Current-Version': this.currentVersion || '0'
            }
          });
          if (response.ok) break;
          lastErr = new Error(`API returned ${response.status}: ${response.statusText}`);
        } catch (err) {
          lastErr = err;
          console.warn(`[DynamicRuleUpdater] ${baseUrl} failed, trying next...`);
        }
      }

      if (!response || !response.ok) {
        throw lastErr || new Error('All API endpoints failed');
      }

      const data = await response.json();
      const { version, rules, metadata } = data;

      // Check if we already have this version
      if (this.currentVersion && version <= this.currentVersion) {
        console.log(`[DynamicRuleUpdater] Already up to date (v${this.currentVersion})`);
        this.isUpdating = false;
        return;
      }

      // Verify checksum
      if (metadata.checksum) {
        const calculatedChecksum = await this.calculateChecksum(rules);
        if (calculatedChecksum !== metadata.checksum) {
          throw new Error('Checksum mismatch - possible tampering detected');
        }
        console.log('[DynamicRuleUpdater] Checksum verified ✓');
      }

      // Save current rules to history before updating
      if (this.currentVersion) {
        await this.saveToHistory(this.currentVersion);
      }

      // Apply new rules
      await this.applyRulesToRegistry(rules);

      // Store in chrome.storage
      await chrome.storage.local.set({
        dynamicRules: rules,
        rulesVersion: version,
        lastRuleUpdate: Date.now(),
        ruleMetadata: metadata
      });

      // Update state
      this.currentVersion = version;
      this.lastUpdateTime = Date.now();
      this.stats.successfulUpdates++;
      this.stats.rulesApplied = rules.length;

      console.log(`[DynamicRuleUpdater] ✓ Updated to v${version} (${rules.length} rules)`);

      // Send telemetry
      await this.sendUpdateTelemetry({
        oldVersion: this.currentVersion - 1,
        newVersion: version,
        ruleCount: rules.length,
        success: true
      });

    } catch (error) {
      console.error('[DynamicRuleUpdater] Update failed:', error);
      this.stats.failedUpdates++;
      this.stats.lastError = error.message;

      // If update fails, try to use cached rules
      await this.fallbackToCachedRules();

      // Send error telemetry
      await this.sendUpdateTelemetry({
        oldVersion: this.currentVersion,
        newVersion: null,
        success: false,
        error: error.message
      });

    } finally {
      this.isUpdating = false;
    }
  }

  /**
   * Apply rules to the rule registry.
   * Detects schema version and routes v2.0.0 rules to EnhancedRuleEngine.
   */
  async applyRulesToRegistry(rules) {
    // Handle v2.0.0/v3.0.0 rulesets (can be a single ruleset object or array of rulesets)
    if (!Array.isArray(rules) && (rules.schemaVersion === '2.0.0' || rules.schemaVersion === '3.0.0')) {
      // Single enhanced ruleset envelope
      await this.ruleRegistry.loadRules(rules.category, rules.subcategory, rules);
      console.log(`[DynamicRuleUpdater] Applied ${rules.schemaVersion} ruleset: ${rules.rulesetId}`);
      return;
    }

    if (Array.isArray(rules)) {
      // Mixed array: separate v1 and enhanced rules
      const v1Rules = [];
      const enhancedRulesets = [];

      for (const rule of rules) {
        if (rule.schemaVersion === '2.0.0' || rule.schemaVersion === '3.0.0') {
          enhancedRulesets.push(rule);
        } else {
          v1Rules.push(rule);
        }
      }

      // Load enhanced rulesets
      for (const ruleset of enhancedRulesets) {
        await this.ruleRegistry.loadRules(ruleset.category, ruleset.subcategory, ruleset);
        console.log(`[DynamicRuleUpdater] Applied ${ruleset.schemaVersion} ruleset: ${ruleset.rulesetId}`);
      }

      // Load v1 rules via legacy path
      if (v1Rules.length > 0) {
        const rulesData = {
          version: this.currentVersion || 'dynamic',
          compiled_at: new Date().toISOString(),
          patterns: v1Rules,
        };
        await this.ruleRegistry.loadRules('dynamic', 'api-rules', rulesData);
        console.log(`[DynamicRuleUpdater] Applied ${v1Rules.length} v1 rules to registry`);
      }

      console.log(`[DynamicRuleUpdater] Applied ${enhancedRulesets.length} enhanced rulesets + ${v1Rules.length} v1 rules`);
      return;
    }

    throw new Error('Rules must be an array or a v2/v3 ruleset object');
  }

  /**
   * Calculate SHA-256 checksum of rules (matches server's crypto.createHash('sha256'))
   */
  async calculateChecksum(rules) {
    const content = JSON.stringify(rules);
    const encoder = new TextEncoder();
    const data = encoder.encode(content);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
  }

  /**
   * Save current rules to history for rollback
   */
  async saveToHistory(version) {
    try {
      const stored = await chrome.storage.local.get(['dynamicRules']);
      if (!stored.dynamicRules) return;

      this.ruleHistory.unshift({
        version,
        rules: stored.dynamicRules,
        timestamp: Date.now()
      });

      // Keep only last N versions
      if (this.ruleHistory.length > this.maxHistorySize) {
        this.ruleHistory = this.ruleHistory.slice(0, this.maxHistorySize);
      }

      // Store history
      await chrome.storage.local.set({
        ruleHistory: this.ruleHistory
      });

      console.log(`[DynamicRuleUpdater] Saved v${version} to history`);
    } catch (error) {
      console.error('[DynamicRuleUpdater] Failed to save history:', error);
    }
  }

  /**
   * Rollback to previous version
   */
  async rollback(targetVersion = null) {
    try {
      console.log(`[DynamicRuleUpdater] Rolling back...`);

      let targetRules = null;

      if (targetVersion) {
        // Rollback to specific version
        const entry = this.ruleHistory.find(h => h.version === targetVersion);
        if (!entry) {
          throw new Error(`Version ${targetVersion} not found in history`);
        }
        targetRules = entry.rules;
      } else {
        // Rollback to previous version
        if (this.ruleHistory.length === 0) {
          throw new Error('No previous version available');
        }
        targetRules = this.ruleHistory[0].rules;
        targetVersion = this.ruleHistory[0].version;
      }

      // Apply rollback rules
      await this.applyRulesToRegistry(targetRules);

      // Update storage
      await chrome.storage.local.set({
        dynamicRules: targetRules,
        rulesVersion: targetVersion,
        lastRuleUpdate: Date.now()
      });

      this.currentVersion = targetVersion;
      this.lastUpdateTime = Date.now();

      console.log(`[DynamicRuleUpdater] ✓ Rolled back to v${targetVersion}`);
      return true;

    } catch (error) {
      console.error('[DynamicRuleUpdater] Rollback failed:', error);
      return false;
    }
  }

  /**
   * Fallback to cached rules if update fails
   */
  async fallbackToCachedRules() {
    try {
      const stored = await chrome.storage.local.get(['dynamicRules', 'rulesVersion']);

      if (stored.dynamicRules) {
        console.log('[DynamicRuleUpdater] Using cached rules from storage');
        await this.applyRulesToRegistry(stored.dynamicRules);
        this.currentVersion = stored.rulesVersion;
        return true;
      }

      console.warn('[DynamicRuleUpdater] No cached rules available');
      return false;

    } catch (error) {
      console.error('[DynamicRuleUpdater] Fallback failed:', error);
      return false;
    }
  }

  /**
   * Schedule next update
   */
  scheduleNextUpdate() {
    // Clear existing timer
    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
    }

    // Schedule next update
    const intervalMs = this.updateInterval * 60 * 60 * 1000; // hours to ms

    this.updateTimer = setTimeout(() => {
      this.checkForUpdates();
      this.scheduleNextUpdate(); // Reschedule
    }, intervalMs);

    const nextUpdate = new Date(Date.now() + intervalMs);
    console.log(`[DynamicRuleUpdater] Next update scheduled for ${nextUpdate.toLocaleString()}`);
  }

  /**
   * Force update now (for manual refresh)
   */
  async forceUpdate() {
    console.log('[DynamicRuleUpdater] Forcing update check...');
    await this.checkForUpdates();
  }

  /**
   * Send telemetry about rule updates
   */
  async sendUpdateTelemetry(data) {
    try {
      await fetch(`${this.apiUrl}/v1/telemetry`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          extension_version: chrome.runtime.getManifest().version,
          rule_version: this.currentVersion,
          events: [{
            type: 'rule_update',
            timestamp: new Date().toISOString(),
            ...data
          }]
        })
      });
    } catch (error) {
      // Silently fail telemetry
      console.debug('[DynamicRuleUpdater] Telemetry failed:', error.message);
    }
  }

  /**
   * Update DNR rules (declarativeNetRequest)
   */
  async updateDNRRules() {
    try {
      console.log('[DynamicRuleUpdater] Updating DNR rules...');

      const response = await fetch(`${this.apiUrl}/v1/dnr/rules`);
      if (!response.ok) {
        throw new Error(`DNR API returned ${response.status}`);
      }

      const data = await response.json();
      const { rules } = data;

      // Get current dynamic rule IDs
      const currentRules = await chrome.declarativeNetRequest.getDynamicRules();
      const currentIds = currentRules.map(r => r.id);

      // Update dynamic rules
      await chrome.declarativeNetRequest.updateDynamicRules({
        removeRuleIds: currentIds,
        addRules: rules
      });

      console.log(`[DynamicRuleUpdater] ✓ Updated ${rules.length} DNR rules`);
      return true;

    } catch (error) {
      console.error('[DynamicRuleUpdater] DNR update failed:', error);
      return false;
    }
  }

  /**
   * Get updater status
   */
  getStatus() {
    return {
      currentVersion: this.currentVersion,
      lastUpdateTime: this.lastUpdateTime,
      nextUpdateTime: this.lastUpdateTime + (this.updateInterval * 60 * 60 * 1000),
      isUpdating: this.isUpdating,
      stats: this.stats,
      historySize: this.ruleHistory.length,
      config: {
        apiUrl: this.apiUrl,
        updateInterval: this.updateInterval
      }
    };
  }

  /**
   * Clean up
   */
  destroy() {
    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
      this.updateTimer = null;
    }
    console.log('[DynamicRuleUpdater] Destroyed');
  }
}