/**
 * Differential Privacy Module for SupTech Telemetry
 * 
 * Implements privacy-preserving aggregation for Central Bank reporting.
 * Allows Bank of Uganda to see regional economic health without
 * exposing individual merchant-customer transaction pairings.
 * 
 * Key Features:
 * - Laplacian noise injection for aggregate statistics
 * - Privacy budget (epsilon) tracking per query
 * - Composition theorems for multiple queries
 * - Regional anonymization for Dark Mode transactions
 */

import crypto from 'crypto';
import { EventEmitter } from 'events';
import { logger } from './lib/logger';

const dpLogger = logger.child('dp');

export interface DPConfig {
  epsilon: number;           // Privacy budget per query (lower = more private)
  delta: number;             // Probability of privacy failure
  sensitivity: number;       // Maximum change from single record
  budgetResetHours: number;  // How often to reset privacy budget
  minAggregationSize: number; // Minimum records for aggregation
}

export interface PrivacyBudget {
  totalEpsilon: number;
  usedEpsilon: number;
  remainingEpsilon: number;
  queryCount: number;
  lastReset: Date;
  nextReset: Date;
}

export interface AggregateResult {
  region: string;
  metric: string;
  noisyValue: number;
  trueValue: number; // Only stored internally, never exposed
  noise: number;
  epsilon: number;
  recordCount: number;
  timestamp: Date;
  privacyGuarantee: string;
}

export interface RegionalTelemetry {
  region: string;
  transactionVolume: number;
  transactionCount: number;
  avgTransactionSize: number;
  uniqueMerchants: number;
  peakHour: number;
  successRate: number;
}

/**
 * Differential Privacy Engine for SupTech Reporting
 */
export class DifferentialPrivacyEngine extends EventEmitter {
  private config: DPConfig;
  private budget: PrivacyBudget;
  private queryHistory: Array<{ query: string; epsilon: number; timestamp: Date }> = [];
  private aggregateCache: Map<string, AggregateResult> = new Map();

  constructor(config?: Partial<DPConfig>) {
    super();

    this.config = {
      epsilon: 1.0,           // Standard epsilon for good utility
      delta: 1e-6,            // Very low failure probability
      sensitivity: 10000,     // Max change from single transaction (UGX)
      budgetResetHours: 24,   // Daily budget reset
      minAggregationSize: 10, // At least 10 records for aggregation
      ...config
    };

    this.budget = {
      totalEpsilon: this.config.epsilon * 100, // Total budget per period
      usedEpsilon: 0,
      remainingEpsilon: this.config.epsilon * 100,
      queryCount: 0,
      lastReset: new Date(),
      nextReset: new Date(Date.now() + this.config.budgetResetHours * 60 * 60 * 1000)
    };

    dpLogger.info('Differential Privacy Engine initialized', { metadata: { epsilon: this.config.epsilon } });
  }

  /**
   * Add Laplacian noise to a value for differential privacy
   */
  addLaplacianNoise(value: number, sensitivity?: number): { noisyValue: number; noise: number } {
    const scale = (sensitivity || this.config.sensitivity) / this.config.epsilon;
    const noise = this.laplacianSample(scale);
    
    return {
      noisyValue: value + noise,
      noise
    };
  }

  /**
   * Generate aggregate with differential privacy for regional telemetry
   * This is what gets sent to Bank of Uganda SupTech Gateway
   */
  generatePrivateAggregate(
    region: string,
    transactions: Array<{ amount: number; merchantId: string; timestamp: Date }>
  ): AggregateResult | null {
    // Check minimum aggregation size (k-anonymity protection)
    if (transactions.length < this.config.minAggregationSize) {
      dpLogger.warn('Insufficient records for aggregation', { metadata: { transactionCount: transactions.length, minAggregationSize: this.config.minAggregationSize } });
      return null;
    }

    // Check privacy budget
    if (this.budget.remainingEpsilon < this.config.epsilon) {
      dpLogger.warn('Privacy budget exhausted - query rejected');
      this.emit('budgetExhausted', { region, budget: this.budget });
      return null;
    }

    // Calculate true aggregate
    const trueValue = transactions.reduce((sum, t) => sum + t.amount, 0);

    // Add calibrated Laplacian noise
    const { noisyValue, noise } = this.addLaplacianNoise(trueValue);

    // Consume privacy budget
    this.consumeBudget(this.config.epsilon);

    const result: AggregateResult = {
      region,
      metric: 'transaction_volume',
      noisyValue: Math.max(0, noisyValue), // Clamp to non-negative
      trueValue, // Internal only
      noise,
      epsilon: this.config.epsilon,
      recordCount: transactions.length,
      timestamp: new Date(),
      privacyGuarantee: `(${this.config.epsilon}, ${this.config.delta})-DP`
    };

    // Cache result
    const cacheKey = `${region}-${Date.now()}`;
    this.aggregateCache.set(cacheKey, result);

    dpLogger.info('Private aggregate generated', { metadata: { region, noisyValueUgx: Number(noisyValue.toFixed(2)) } });
    this.emit('aggregateGenerated', { region, noisyValue, epsilon: this.config.epsilon });

    return result;
  }

  /**
   * Generate regional telemetry with privacy protection
   */
  generateRegionalTelemetry(
    region: string,
    transactions: Array<{ amount: number; merchantId: string; timestamp: Date; success: boolean }>
  ): RegionalTelemetry | null {
    if (transactions.length < this.config.minAggregationSize) {
      return null;
    }

    // Check budget for multiple queries (composition)
    const queriesNeeded = 6; // One for each metric
    const epsilonNeeded = this.config.epsilon * queriesNeeded;
    
    if (this.budget.remainingEpsilon < epsilonNeeded) {
      dpLogger.warn('Insufficient budget for full telemetry');
      return null;
    }

    // Add noise to each metric
    const totalVolume = transactions.reduce((sum, t) => sum + t.amount, 0);
    const uniqueMerchants = new Set(transactions.map(t => t.merchantId)).size;
    const avgSize = totalVolume / transactions.length;
    const successCount = transactions.filter(t => t.success).length;
    
    // Find peak hour
    const hourCounts = new Array(24).fill(0);
    transactions.forEach(t => {
      const hour = t.timestamp.getHours();
      hourCounts[hour]++;
    });
    const peakHour = hourCounts.indexOf(Math.max(...hourCounts));

    const telemetry: RegionalTelemetry = {
      region,
      transactionVolume: Math.max(0, this.addLaplacianNoise(totalVolume).noisyValue),
      transactionCount: Math.max(0, Math.round(this.addLaplacianNoise(transactions.length, 1).noisyValue)),
      avgTransactionSize: Math.max(0, this.addLaplacianNoise(avgSize, avgSize * 0.1).noisyValue),
      uniqueMerchants: Math.max(1, Math.round(this.addLaplacianNoise(uniqueMerchants, 1).noisyValue)),
      peakHour: Math.min(23, Math.max(0, Math.round(this.addLaplacianNoise(peakHour, 1).noisyValue))),
      successRate: Math.min(100, Math.max(0, this.addLaplacianNoise((successCount / transactions.length) * 100, 1).noisyValue))
    };

    // Consume budget for all queries
    this.consumeBudget(epsilonNeeded);

    dpLogger.info('Regional telemetry generated', { metadata: { region, transactionCount: telemetry.transactionCount } });
    return telemetry;
  }

  /**
   * Generate privacy-preserving report for SupTech Gateway
   */
  generateSupTechReport(
    regions: Map<string, Array<{ amount: number; merchantId: string; timestamp: Date; success: boolean }>>
  ): {
    reportId: string;
    timestamp: Date;
    privacyLevel: string;
    regions: RegionalTelemetry[];
    budgetRemaining: number;
  } {
    const regionalData: RegionalTelemetry[] = [];

    regions.forEach((transactions, region) => {
      const telemetry = this.generateRegionalTelemetry(region, transactions);
      if (telemetry) {
        regionalData.push(telemetry);
      }
    });

    return {
      reportId: `SUPTECH-${Date.now().toString(36).toUpperCase()}`,
      timestamp: new Date(),
      privacyLevel: `(${this.config.epsilon}, ${this.config.delta})-Differential Privacy`,
      regions: regionalData,
      budgetRemaining: this.budget.remainingEpsilon
    };
  }

  /**
   * Get current privacy budget status
   */
  getBudgetStatus(): PrivacyBudget {
    this.checkBudgetReset();
    return { ...this.budget };
  }

  /**
   * Get configuration
   */
  getConfig(): DPConfig {
    return { ...this.config };
  }

  /**
   * Get query history (for audit purposes)
   */
  getQueryHistory(): Array<{ query: string; epsilon: number; timestamp: Date }> {
    return [...this.queryHistory];
  }

  /**
   * Reset privacy budget (called at period boundaries)
   */
  resetBudget(): void {
    this.budget = {
      totalEpsilon: this.config.epsilon * 100,
      usedEpsilon: 0,
      remainingEpsilon: this.config.epsilon * 100,
      queryCount: 0,
      lastReset: new Date(),
      nextReset: new Date(Date.now() + this.config.budgetResetHours * 60 * 60 * 1000)
    };

    dpLogger.info('Privacy budget reset');
    this.emit('budgetReset', this.budget);
  }

  /**
   * Anonymize transaction for Dark Mode storage
   * Removes PII while preserving aggregate analysis capability
   */
  anonymizeTransaction(transaction: {
    id: string;
    amount: number;
    merchantId: string;
    customerId: string;
    timestamp: Date;
    location: { lat: number; lng: number };
  }): {
    anonymizedId: string;
    amount: number;
    merchantRegion: string;
    timeSlot: string;
    generalLocation: string;
  } {
    // Hash IDs to prevent re-identification
    const anonymizedId = crypto.createHash('sha256')
      .update(transaction.id + 'salt')
      .digest('hex').substring(0, 16);

    // Generalize merchant to region (first 4 chars of hash)
    const merchantRegion = crypto.createHash('sha256')
      .update(transaction.merchantId)
      .digest('hex').substring(0, 4);

    // Generalize time to hour slots
    const hour = transaction.timestamp.getHours();
    const timeSlot = `${Math.floor(hour / 4) * 4}-${Math.floor(hour / 4) * 4 + 3}`;

    // Generalize location to grid cell (0.1 degree ~ 11km)
    const latCell = Math.floor(transaction.location.lat * 10) / 10;
    const lngCell = Math.floor(transaction.location.lng * 10) / 10;
    const generalLocation = `${latCell},${lngCell}`;

    return {
      anonymizedId,
      amount: transaction.amount,
      merchantRegion,
      timeSlot,
      generalLocation
    };
  }

  // Private methods

  private laplacianSample(scale: number): number {
    // Generate Laplacian noise using inverse CDF method
    const u = Math.random() - 0.5;
    return -scale * Math.sign(u) * Math.log(1 - 2 * Math.abs(u));
  }

  private consumeBudget(epsilon: number): void {
    this.budget.usedEpsilon += epsilon;
    this.budget.remainingEpsilon = this.budget.totalEpsilon - this.budget.usedEpsilon;
    this.budget.queryCount++;

    this.queryHistory.push({
      query: `aggregate_query_${this.budget.queryCount}`,
      epsilon,
      timestamp: new Date()
    });

    // Keep only last 1000 queries in history
    if (this.queryHistory.length > 1000) {
      this.queryHistory = this.queryHistory.slice(-1000);
    }
  }

  private checkBudgetReset(): void {
    if (new Date() >= this.budget.nextReset) {
      this.resetBudget();
    }
  }
}

// Singleton instance
let globalDPEngine: DifferentialPrivacyEngine | null = null;

export function getGlobalDPEngine(): DifferentialPrivacyEngine {
  if (!globalDPEngine) {
    globalDPEngine = new DifferentialPrivacyEngine();
  }
  return globalDPEngine;
}
