// Telemetry Reporter - Sends detection events to cloud for threat intelligence
// Implements privacy-first design: SHA256 hashing, feature extraction, no PII

export class TelemetryReporter {
  constructor(config = {}) {
    this.config = config;
    this.enabled = config.telemetryEnabled !== false;
    this.endpoint = config.telemetryEndpoint || 'https://api.phishprotect.cloud/api/v1/telemetry';
    this.apiKey = config.apiKey || '';
    this.deviceId = config.deviceId || this.generateDeviceId();
    this.extensionVersion = chrome.runtime.getManifest().version;
    this.organizationId = config.organizationId || null;

    // Batching configuration
    this.batchEnabled = config.batchTelemetry !== false;
    this.batchSize = config.batchSize || 100;
    this.batchTimeout = config.batchTimeout || 60000; // 1 minute
    this.eventQueue = [];
    this.batchTimer = null;

    // Privacy settings
    this.differentialPrivacy = config.differentialPrivacy !== false;
    this.kAnonymityThreshold = config.kAnonymityThreshold || 5;
  }

  /**
   * Report detection event to cloud
   * @param {object} detection - Detection result from analyzer
   * @param {object} target - Target being analyzed
   * @param {object} context - Additional context
   */
  async reportDetection(detection, target, context = {}) {
    if (!this.enabled || !this.apiKey) {
      return;
    }

    try {
      // Determine if high or low fidelity
      const isHighFidelity = this.isHighFidelityDetection(detection);

      // Build telemetry event
      const event = {
        eventType: isHighFidelity ? 'detection-high' : 'detection-low',
        detection: isHighFidelity
          ? this.buildHighFidelityDetection(detection)
          : this.buildLowFidelityDetection(detection),
        resource: await this.buildResourceInfo(target),
        context: this.buildContextInfo(context),
        contentFeatures: await this.extractContentFeatures(detection, target),
        device: this.buildDeviceInfo()
      };

      // Add to batch or send immediately
      if (this.batchEnabled) {
        this.addToBatch(event);
      } else {
        await this.sendEvent(event);
      }
    } catch (error) {
      console.error('[PhishProtect] Telemetry reporting error:', error);
    }
  }

  /**
   * Determine if detection is high fidelity (definite threat)
   * @param {object} detection - Detection result
   * @returns {boolean} - True if high fidelity
   */
  isHighFidelityDetection(detection) {
    // High fidelity criteria:
    // 1. Risk score >= 15 (critical or high)
    // 2. Contains critical severity findings
    // 3. Specific high-confidence detectors

    if (detection.riskScore >= 15) {
      return true;
    }

    const criticalFindings = detection.findings?.filter(f => f.severity === 'critical') || [];
    if (criticalFindings.length > 0) {
      return true;
    }

    // High-confidence detectors that always produce high-fidelity signals
    const highConfidenceDetectors = [
      'clipboard-injection',
      'html-smuggling',
      'formjacking',
      'crypto-address-swap',
      'mshta-execution',
      'encoded-powershell'
    ];

    const hasHighConfidenceDetector = detection.findings?.some(f =>
      highConfidenceDetectors.includes(f.detector)
    );

    return hasHighConfidenceDetector;
  }

  /**
   * Build high-fidelity detection object
   * @param {object} detection - Detection result
   * @returns {object} - High-fidelity detection
   */
  buildHighFidelityDetection(detection) {
    return {
      detector: detection.detector,
      matched: detection.matched,
      findings: detection.findings.map(f => ({
        detector: f.detector,
        type: f.type,
        severity: f.severity,
        weight: f.weight,
        description: f.description || '',
        mitre: f.mitre || []
      })),
      riskScore: detection.riskScore,
      riskLevel: this.getRiskLevel(detection.riskScore)
    };
  }

  /**
   * Build low-fidelity detection object
   * @param {object} detection - Detection result
   * @returns {object} - Low-fidelity detection
   */
  buildLowFidelityDetection(detection) {
    return {
      detector: detection.detector,
      suspicionScore: detection.riskScore,
      pageFeatures: detection.metadata?.pageFeatures || {},
      behaviorFeatures: detection.metadata?.behaviorFeatures || {},
      textFeatures: detection.metadata?.textFeatures || {}
    };
  }

  /**
   * Build resource information (privacy-preserving)
   * @param {object} target - Target being analyzed
   * @returns {object} - Resource info with hashed identifiers
   */
  async buildResourceInfo(target) {
    const resource = {};

    if (target.url) {
      const url = new URL(target.url);

      // Hash URL components for privacy
      resource.urlHash = await this.sha256(target.url);
      resource.domainHash = await this.sha256(url.hostname);
      resource.pathHash = await this.sha256(url.pathname);

      // Non-sensitive metadata
      resource.tld = this.extractTLD(url.hostname);
      resource.isIP = this.isIPAddress(url.hostname);
      resource.hasNonStandardPort = url.port && !['80', '443', '8080'].includes(url.port);
    }

    return resource;
  }

  /**
   * Build context information
   * @param {object} context - Context data
   * @returns {object} - Context info
   */
  buildContextInfo(context) {
    return {
      referrerHash: context.referrer ? this.sha256Sync(context.referrer) : null,
      tabAge: context.tabAge || null,
      userInteraction: context.userInteraction || false,
      pageLoadTime: context.pageLoadTime || null,
      timestamp: Date.now()
    };
  }

  /**
   * Extract content features from detection
   * @param {object} detection - Detection result
   * @param {object} target - Target being analyzed
   * @returns {object} - Content features
   */
  async extractContentFeatures(detection, target) {
    // Only extract features for clipboard injection and similar content-based detections
    if (!detection.findings || detection.findings.length === 0) {
      return null;
    }

    // Check if this is a content-based detection
    const contentDetectors = [
      'clipboard-injection',
      'keyboard-instruction',
      'formjacking',
      'html-smuggling'
    ];

    const isContentDetection = detection.findings.some(f =>
      contentDetectors.includes(f.detector)
    );

    if (!isContentDetection) {
      return null;
    }

    // Extract features from evidence
    const features = {
      contentHash: null,
      contentLength: 0,
      commandType: null,
      obfuscationScore: 0,
      extractedFeatures: {},
      contentSnippet: null
    };

    // Find clipboard injection or similar evidence
    for (const finding of detection.findings) {
      if (finding.evidence) {
        const evidence = finding.evidence;

        // Hash the content
        if (evidence.clipboardData || evidence.text) {
          const content = evidence.clipboardData || evidence.text;
          features.contentHash = await this.sha256(content);
          features.contentLength = content.length;

          // Extract snippet (first 50 and last 50 chars)
          features.contentSnippet = {
            prefix: content.substring(0, 50),
            suffix: content.substring(Math.max(0, content.length - 50))
          };
        }

        // Extract command features
        if (evidence.commandType) {
          features.commandType = evidence.commandType;
        }

        if (evidence.obfuscation) {
          features.obfuscationScore = evidence.obfuscation.score || 0;
        }

        // Extract specific features
        if (evidence.techniques) {
          features.extractedFeatures.techniques = evidence.techniques;
        }

        // Extract command verbs (e.g., Invoke-WebRequest, Start-Process)
        if (evidence.commandType) {
          features.extractedFeatures.commandVerbs = this.extractCommandVerbs(
            evidence.clipboardData || evidence.text || ''
          );
        }

        // Extract file extensions
        features.extractedFeatures.fileExtensions = this.extractFileExtensions(
          evidence.clipboardData || evidence.text || ''
        );

        // Extract URL schemes
        features.extractedFeatures.urlSchemes = this.extractUrlSchemes(
          evidence.clipboardData || evidence.text || ''
        );

        // Extract port numbers
        features.extractedFeatures.portNumbers = this.extractPortNumbers(
          evidence.clipboardData || evidence.text || ''
        );

        // Extract IP addresses (count only, not actual IPs)
        features.extractedFeatures.ipAddressCount = this.countIPAddresses(
          evidence.clipboardData || evidence.text || ''
        );
      }
    }

    return features.contentHash ? features : null;
  }

  /**
   * Extract command verbs from text
   * @param {string} text - Text to analyze
   * @returns {array} - Command verbs
   */
  extractCommandVerbs(text) {
    const verbs = new Set();

    const powershellVerbs = [
      'Invoke-WebRequest', 'Invoke-Expression', 'Start-Process', 'DownloadFile',
      'DownloadString', 'Add-MpPreference', 'Set-MpPreference', 'Invoke-Command'
    ];

    const cmdVerbs = [
      'certutil', 'bitsadmin', 'mshta', 'regsvr32', 'rundll32', 'wmic', 'reg'
    ];

    const bashVerbs = [
      'curl', 'wget', 'bash', 'sh', 'python', 'perl', 'nc', 'netcat'
    ];

    [...powershellVerbs, ...cmdVerbs, ...bashVerbs].forEach(verb => {
      if (new RegExp(verb, 'i').test(text)) {
        verbs.add(verb);
      }
    });

    return Array.from(verbs);
  }

  /**
   * Extract file extensions from text
   * @param {string} text - Text to analyze
   * @returns {array} - File extensions
   */
  extractFileExtensions(text) {
    const extensions = new Set();
    const extPattern = /\.(?:exe|bat|cmd|ps1|vbs|js|dll|scr|hta|msi|jar|zip|rar|7z|pdf|docx|xlsx)/gi;
    const matches = text.match(extPattern) || [];

    matches.forEach(ext => extensions.add(ext.toLowerCase()));

    return Array.from(extensions);
  }

  /**
   * Extract URL schemes from text
   * @param {string} text - Text to analyze
   * @returns {array} - URL schemes
   */
  extractUrlSchemes(text) {
    const schemes = new Set();
    const schemePattern = /\b(https?|ftp|file|data|javascript):/gi;
    const matches = text.match(schemePattern) || [];

    matches.forEach(match => {
      const scheme = match.replace(':', '').toLowerCase();
      schemes.add(scheme);
    });

    return Array.from(schemes);
  }

  /**
   * Extract port numbers from text
   * @param {string} text - Text to analyze
   * @returns {array} - Port numbers
   */
  extractPortNumbers(text) {
    const ports = new Set();
    const portPattern = /:(\d{2,5})\//g;
    let match;

    while ((match = portPattern.exec(text)) !== null) {
      const port = parseInt(match[1]);
      if (port > 0 && port <= 65535) {
        ports.add(port);
      }
    }

    return Array.from(ports);
  }

  /**
   * Count IP addresses in text (privacy-preserving)
   * @param {string} text - Text to analyze
   * @returns {number} - IP address count
   */
  countIPAddresses(text) {
    const ipPattern = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;
    const matches = text.match(ipPattern) || [];
    return matches.length;
  }

  /**
   * Build device information
   * @returns {object} - Device info
   */
  buildDeviceInfo() {
    return {
      deviceId: this.deviceId,
      extensionVersion: this.extensionVersion,
      organizationId: this.organizationId,
      policyVersion: this.config.policyVersion || null
    };
  }

  /**
   * Add event to batch queue
   * @param {object} event - Telemetry event
   */
  addToBatch(event) {
    this.eventQueue.push(event);

    // Send batch if size threshold reached
    if (this.eventQueue.length >= this.batchSize) {
      this.flushBatch();
    } else {
      // Reset batch timer
      if (this.batchTimer) {
        clearTimeout(this.batchTimer);
      }

      this.batchTimer = setTimeout(() => {
        this.flushBatch();
      }, this.batchTimeout);
    }
  }

  /**
   * Flush batch queue to cloud
   */
  async flushBatch() {
    if (this.eventQueue.length === 0) {
      return;
    }

    const batch = {
      events: this.eventQueue.splice(0, this.batchSize),
      batchId: this.generateBatchId()
    };

    try {
      await this.sendBatch(batch);
    } catch (error) {
      console.error('[PhishProtect] Batch telemetry error:', error);

      // Re-queue events on failure (up to 3 retries)
      if (batch.retries < 3) {
        batch.retries = (batch.retries || 0) + 1;
        this.eventQueue.unshift(...batch.events);
      }
    }

    // Clear batch timer
    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
      this.batchTimer = null;
    }
  }

  /**
   * Send single event to cloud
   * @param {object} event - Telemetry event
   */
  async sendEvent(event) {
    const response = await fetch(`${this.endpoint}/detection`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey
      },
      body: JSON.stringify(event)
    });

    if (!response.ok) {
      throw new Error(`Telemetry API error: ${response.status}`);
    }

    const result = await response.json();
    console.log('[PhishProtect] Telemetry sent:', result.eventId);

    return result;
  }

  /**
   * Send batch to cloud
   * @param {object} batch - Batch of events
   */
  async sendBatch(batch) {
    const response = await fetch(`${this.endpoint}/batch`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey
      },
      body: JSON.stringify(batch)
    });

    if (!response.ok) {
      throw new Error(`Batch telemetry API error: ${response.status}`);
    }

    const result = await response.json();
    console.log(`[PhishProtect] Batch telemetry sent: ${result.eventCount} events, batchId: ${result.batchId}`);

    return result;
  }

  /**
   * SHA256 hash (async)
   * @param {string} text - Text to hash
   * @returns {string} - Hex hash
   */
  async sha256(text) {
    const encoder = new TextEncoder();
    const data = encoder.encode(text);
    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('');
  }

  /**
   * SHA256 hash (synchronous fallback)
   * @param {string} text - Text to hash
   * @returns {string} - Hex hash (synchronous approximation)
   */
  sha256Sync(text) {
    // Simple hash for non-critical paths
    let hash = 0;
    for (let i = 0; i < text.length; i++) {
      const char = text.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return Math.abs(hash).toString(16).padStart(64, '0');
  }

  /**
   * Extract TLD from hostname
   * @param {string} hostname - Hostname
   * @returns {string} - TLD
   */
  extractTLD(hostname) {
    const parts = hostname.split('.');
    return parts.length > 1 ? parts[parts.length - 1] : null;
  }

  /**
   * Check if hostname is an IP address
   * @param {string} hostname - Hostname
   * @returns {boolean} - True if IP address
   */
  isIPAddress(hostname) {
    return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
  }

  /**
   * Get risk level from score
   * @param {number} riskScore - Risk score
   * @returns {string} - Risk level
   */
  getRiskLevel(riskScore) {
    if (riskScore >= 15) return 'critical';
    if (riskScore >= 10) return 'high';
    if (riskScore >= 5) return 'medium';
    return 'low';
  }

  /**
   * Generate device ID (stable across sessions)
   * @returns {string} - Device ID
   */
  generateDeviceId() {
    // Try to get from storage first
    return chrome.storage.local.get(['deviceId']).then(result => {
      if (result.deviceId) {
        return result.deviceId;
      }

      // Generate new device ID
      const deviceId = crypto.randomUUID();
      chrome.storage.local.set({ deviceId });
      return deviceId;
    });
  }

  /**
   * Generate batch ID
   * @returns {string} - Batch ID
   */
  generateBatchId() {
    return `batch-${Date.now()}-${Math.random().toString(36).substring(7)}`;
  }

  /**
   * Enable/disable telemetry
   * @param {boolean} enabled - Enable flag
   */
  setEnabled(enabled) {
    this.enabled = enabled;

    if (!enabled && this.batchTimer) {
      clearTimeout(this.batchTimer);
      this.eventQueue = [];
    }
  }
}
