// Audit Logger - Logs policy decisions for compliance and forensics
// Privacy-first: No PII, URLs, or sensitive data logged

export class AuditLogger {
  constructor(config = {}) {
    this.config = config;
    this.maxLogSize = config.maxAuditLogs || 10000;
    this.retentionPeriod = config.auditRetentionDays || 90;
    this.enableRemoteLogging = config.enableRemoteAuditLog || false;
    this.remoteEndpoint = config.auditLogEndpoint;
    this.logBuffer = [];
    this.flushInterval = config.auditFlushInterval || 60000; // 1 minute

    this.initializeLogger();
  }

  /**
   * Initialize audit logger
   */
  async initializeLogger() {
    // Load existing logs from storage
    await this.loadLogs();

    // Set up periodic flush
    if (this.enableRemoteLogging) {
      setInterval(() => this.flushLogs(), this.flushInterval);
    }

    // Clean up old logs periodically
    setInterval(() => this.cleanOldLogs(), 86400000); // Daily
  }

  /**
   * Log policy decision
   * @param {object} decision - Policy decision to log
   * @param {object} context - Additional context
   */
  async logDecision(decision, context = {}) {
    const logEntry = {
      id: this.generateLogId(),
      timestamp: Date.now(),
      type: 'policy_decision',
      action: decision.action,
      riskScore: decision.riskScore,
      policyId: context.policyId || 'unknown',
      detectorCount: decision.findings?.length || 0,
      severities: this.aggregateSeverities(decision.findings),
      categories: this.aggregateCategories(decision.findings),
      techniques: this.aggregateTechniques(decision.findings),
      matchedRules: decision.matchedRules?.map(r => r.name) || [],
      enforcement: decision.enforcement?.type || null,
      // NO PII: No URLs, domains, or user identifiable information
      session: this.hashSessionId(context.sessionId)
    };

    this.logBuffer.push(logEntry);

    // Write to storage
    await this.saveLog(logEntry);

    // If buffer is full, flush to remote
    if (this.enableRemoteLogging && this.logBuffer.length >= 100) {
      await this.flushLogs();
    }
  }

  /**
   * Log detector performance metrics
   * @param {string} detectorName - Detector name
   * @param {number} latency - Execution time in ms
   * @param {boolean} matched - Whether detector matched
   */
  async logDetectorPerformance(detectorName, latency, matched) {
    const logEntry = {
      id: this.generateLogId(),
      timestamp: Date.now(),
      type: 'detector_performance',
      detector: detectorName,
      latency,
      matched,
      // Aggregate by hour for privacy
      hour: Math.floor(Date.now() / 3600000)
    };

    await this.saveLog(logEntry);
  }

  /**
   * Log policy change event
   * @param {string} action - Change action (load, update, switch)
   * @param {string} policyId - Policy ID
   * @param {object} metadata - Additional metadata
   */
  async logPolicyChange(action, policyId, metadata = {}) {
    const logEntry = {
      id: this.generateLogId(),
      timestamp: Date.now(),
      type: 'policy_change',
      action,
      policyId,
      source: metadata.source || 'unknown',
      triggeredBy: metadata.triggeredBy || 'system'
    };

    this.logBuffer.push(logEntry);
    await this.saveLog(logEntry);
  }

  /**
   * Log enforcement action
   * @param {string} action - Enforcement action taken
   * @param {object} context - Context information
   */
  async logEnforcement(action, context = {}) {
    const logEntry = {
      id: this.generateLogId(),
      timestamp: Date.now(),
      type: 'enforcement',
      action,
      riskScore: context.riskScore || 0,
      detectorCount: context.detectorCount || 0,
      userOverride: context.userOverride || false,
      session: this.hashSessionId(context.sessionId)
    };

    this.logBuffer.push(logEntry);
    await this.saveLog(logEntry);
  }

  /**
   * Log user-facing action (clipboard block, false positive report, etc.)
   * @param {string} action - Action identifier
   * @param {object} context - Context information
   */
  async logUserAction(action, context = {}) {
    const logEntry = {
      id: this.generateLogId(),
      timestamp: Date.now(),
      type: 'user_action',
      action,
      details: context
    };

    this.logBuffer.push(logEntry);
    await this.saveLog(logEntry);
  }

  /**
   * Get audit logs
   * @param {object} filters - Query filters
   * @returns {array} - Filtered logs
   */
  async getAuditLogs(filters = {}) {
    const logs = await this.loadLogs();

    let filtered = logs;

    if (filters.type) {
      filtered = filtered.filter(log => log.type === filters.type);
    }

    if (filters.action) {
      filtered = filtered.filter(log => log.action === filters.action);
    }

    if (filters.startDate) {
      filtered = filtered.filter(log => log.timestamp >= filters.startDate);
    }

    if (filters.endDate) {
      filtered = filtered.filter(log => log.timestamp <= filters.endDate);
    }

    if (filters.policyId) {
      filtered = filtered.filter(log => log.policyId === filters.policyId);
    }

    return filtered;
  }

  /**
   * Get audit statistics
   * @param {number} timeRange - Time range in ms (default: 24 hours)
   * @returns {object} - Audit statistics
   */
  async getStatistics(timeRange = 86400000) {
    const logs = await this.loadLogs();
    const cutoff = Date.now() - timeRange;

    const recentLogs = logs.filter(log => log.timestamp >= cutoff);

    const stats = {
      totalLogs: logs.length,
      recentLogs: recentLogs.length,
      byType: {},
      byAction: {},
      byEnforcement: {},
      averageRiskScore: 0,
      detectorHits: {}
    };

    // Aggregate statistics
    let riskScoreSum = 0;
    let riskScoreCount = 0;

    for (const log of recentLogs) {
      // By type
      stats.byType[log.type] = (stats.byType[log.type] || 0) + 1;

      // By action
      if (log.action) {
        stats.byAction[log.action] = (stats.byAction[log.action] || 0) + 1;
      }

      // By enforcement
      if (log.enforcement) {
        stats.byEnforcement[log.enforcement] =
          (stats.byEnforcement[log.enforcement] || 0) + 1;
      }

      // Risk score average
      if (log.riskScore !== undefined) {
        riskScoreSum += log.riskScore;
        riskScoreCount++;
      }

      // Detector hits
      if (log.detector) {
        stats.detectorHits[log.detector] = (stats.detectorHits[log.detector] || 0) + 1;
      }
    }

    if (riskScoreCount > 0) {
      stats.averageRiskScore = Math.round(riskScoreSum / riskScoreCount);
    }

    return stats;
  }

  /**
   * Flush logs to remote endpoint
   */
  async flushLogs() {
    if (!this.enableRemoteLogging || this.logBuffer.length === 0) {
      return;
    }

    if (!this.remoteEndpoint) {
      console.warn('Remote logging enabled but no endpoint configured');
      return;
    }

    try {
      const batch = [...this.logBuffer];
      this.logBuffer = [];

      const response = await fetch(this.remoteEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          logs: batch,
          source: 'phishprotect',
          version: chrome.runtime.getManifest().version
        })
      });

      if (!response.ok) {
        console.error('Failed to flush audit logs:', response.status);
        // Re-add to buffer
        this.logBuffer.unshift(...batch);
      }
    } catch (error) {
      console.error('Error flushing audit logs:', error);
    }
  }

  /**
   * Clean old logs based on retention period
   */
  async cleanOldLogs() {
    const logs = await this.loadLogs();
    const cutoff = Date.now() - (this.retentionPeriod * 86400000);

    const retainedLogs = logs.filter(log => log.timestamp >= cutoff);

    // Also enforce max log size
    const finalLogs = retainedLogs.slice(-this.maxLogSize);

    await chrome.storage.local.set({
      auditLogs: finalLogs
    });

    console.log(`Cleaned audit logs: ${logs.length} -> ${finalLogs.length}`);
  }

  /**
   * Save log entry to storage
   * @param {object} logEntry - Log entry to save
   */
  async saveLog(logEntry) {
    try {
      const result = await chrome.storage.local.get(['auditLogs']);
      const logs = result.auditLogs || [];

      logs.push(logEntry);

      // Enforce max size
      const trimmedLogs = logs.slice(-this.maxLogSize);

      await chrome.storage.local.set({
        auditLogs: trimmedLogs
      });
    } catch (error) {
      console.error('Failed to save audit log:', error);
    }
  }

  /**
   * Load logs from storage
   * @returns {array} - Audit logs
   */
  async loadLogs() {
    try {
      const result = await chrome.storage.local.get(['auditLogs']);
      return result.auditLogs || [];
    } catch (error) {
      console.error('Failed to load audit logs:', error);
      return [];
    }
  }

  /**
   * Export logs for external analysis
   * @param {string} format - Export format (json, csv)
   * @returns {string} - Exported data
   */
  async exportLogs(format = 'json') {
    const logs = await this.loadLogs();

    if (format === 'json') {
      return JSON.stringify(logs, null, 2);
    }

    if (format === 'csv') {
      return this.convertToCSV(logs);
    }

    throw new Error(`Unsupported format: ${format}`);
  }

  /**
   * Convert logs to CSV format
   * @param {array} logs - Logs to convert
   * @returns {string} - CSV data
   */
  convertToCSV(logs) {
    if (logs.length === 0) return '';

    const headers = ['id', 'timestamp', 'type', 'action', 'riskScore', 'policyId'];
    const rows = [headers.join(',')];

    for (const log of logs) {
      const row = headers.map(h => log[h] || '').join(',');
      rows.push(row);
    }

    return rows.join('\n');
  }

  /**
   * Aggregate severities from findings
   * @param {array} findings - Detection findings
   * @returns {object} - Severity counts
   */
  aggregateSeverities(findings = []) {
    const severities = {};

    for (const finding of findings) {
      const severity = finding.severity || 'unknown';
      severities[severity] = (severities[severity] || 0) + 1;
    }

    return severities;
  }

  /**
   * Aggregate categories from findings
   * @param {array} findings - Detection findings
   * @returns {array} - Unique categories
   */
  aggregateCategories(findings = []) {
    const categories = new Set();

    for (const finding of findings) {
      if (finding.category) {
        categories.add(finding.category);
      }
    }

    return Array.from(categories);
  }

  /**
   * Aggregate techniques from findings
   * @param {array} findings - Detection findings
   * @returns {array} - Unique techniques
   */
  aggregateTechniques(findings = []) {
    const techniques = new Set();

    for (const finding of findings) {
      if (finding.metadata?.technique) {
        techniques.add(finding.metadata.technique);
      }
    }

    return Array.from(techniques);
  }

  /**
   * Generate unique log ID
   * @returns {string} - Unique ID
   */
  generateLogId() {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * Hash session ID for privacy
   * @param {string} sessionId - Original session ID
   * @returns {string} - Hashed session ID
   */
  hashSessionId(sessionId) {
    if (!sessionId) return null;

    // Simple hash for privacy (in production, use crypto.subtle.digest)
    let hash = 0;
    for (let i = 0; i < sessionId.length; i++) {
      const char = sessionId.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }

    return `session_${Math.abs(hash).toString(36)}`;
  }

  /**
   * Clear all audit logs (admin function)
   */
  async clearAllLogs() {
    await chrome.storage.local.set({
      auditLogs: []
    });

    this.logBuffer = [];
    console.log('All audit logs cleared');
  }
}
