/**
 * Post-Quantum Cryptography Engine (ML-KEM-768)
 * NIST FIPS 203 compliant lattice-based key encapsulation
 * 
 * Patent Reference: AEGIS-PAT-2026-001
 * Component: Security Module (230)
 * 
 * Algorithm: ML-KEM-768 (Module-Lattice Key Encapsulation Mechanism)
 * Security Level: NIST Level 3 (equivalent to AES-192)
 * Key Sizes:
 *   - Public Key: 1184 bytes
 *   - Secret Key: 2400 bytes
 *   - Ciphertext: 1088 bytes
 *   - Shared Secret: 32 bytes
 * 
 * Implementation status:
 *   - ML-KEM-768 KEM (MLKEMEngine.generateKeyPair / encapsulate / decapsulate) is REAL,
 *     FIPS 203-conformant via @noble/post-quantum (ml_kem768), verified BYTE-EXACT against
 *     NIST ACVP FIPS-203 KAT vectors (scripts/verify-mlkem768-real.ts raw + 
 *     scripts/verify-mlkem768-engine.ts through the engine API; vectors + provenance in
 *     tests/fixtures/mlkem768/). AS/PLATFORM/2026/005.
 *   - The /api/pqc/handshake route is demo/orchestration: the KEM primitive is real, but the
 *     route does not return ciphertext to a terminal and uses a zero-filled mock public key,
 *     so it is NOT a complete external PQC handshake.
 */

import * as crypto from 'crypto';
import { EventEmitter } from 'events';
import { ml_kem768 } from '@noble/post-quantum/ml-kem.js';
import { logger } from './lib/logger';

const pqcLogger = logger.child('pqc');

// ML-KEM-768 constants (NIST FIPS 203)
const ML_KEM_768 = {
  name: 'ML-KEM-768',
  publicKeySize: 1184,
  secretKeySize: 2400,
  ciphertextSize: 1088,
  sharedSecretSize: 32,
  securityLevel: 3,
  nistStandard: 'FIPS 203'
};

export interface PQCKeyPair {
  publicKey: Buffer;
  secretKey: Buffer;
  algorithm: string;
  createdAt: Date;
  fingerprint: string;
}

export interface PQCEncapsulation {
  ciphertext: Buffer;
  sharedSecret: Buffer;
  algorithm: string;
  timestamp: Date;
}

export interface PQCSession {
  sessionId: string;
  nodeId: string;
  sharedSecret: Buffer;
  establishedAt: Date;
  expiresAt: Date;
  messageCount: number;
}

export interface PQCSignature {
  signature: Buffer;
  algorithm: string;
  timestamp: Date;
  nodeId: string;
}

/**
 * ML-KEM-768 Key Encapsulation Mechanism
 * Provides quantum-resistant key exchange
 */
export class MLKEMEngine {
  private keyPair: PQCKeyPair | null = null;
  private sessions: Map<string, PQCSession> = new Map();
  private nodeId: string;

  constructor(nodeId: string) {
    this.nodeId = nodeId;
  }

  /**
   * Generates a new ML-KEM-768 key pair.
   * Real FIPS 203 ML-KEM-768 key generation (via @noble/post-quantum).
   */
  generateKeyPair(): PQCKeyPair {
    // Real ML-KEM-768 key generation (FIPS 203) via @noble/post-quantum.
    // keygen() draws a fresh 64-byte (d‖z) seed from the platform CSPRNG internally.
    const kp = ml_kem768.keygen();
    const publicKey = Buffer.from(kp.publicKey); // 1184 bytes (ek)
    const secretKey = Buffer.from(kp.secretKey); // 2400 bytes (dk)

    // Create key fingerprint for identification
    const fingerprint = crypto
      .createHash('sha256')
      .update(publicKey)
      .digest('hex')
      .substring(0, 16);

    this.keyPair = {
      publicKey,
      secretKey,
      algorithm: ML_KEM_768.name,
      createdAt: new Date(),
      fingerprint
    };

    pqcLogger.info('Generated key pair', {
      metadata: {
        algorithm: ML_KEM_768.name,
        publicKeyBytes: ML_KEM_768.publicKeySize,
        secretKeyBytes: ML_KEM_768.secretKeySize,
        fingerprint
      }
    });

    return this.keyPair;
  }

  /**
   * Encapsulates a shared secret using the recipient's public key.
   * Returns ciphertext that only the secret key holder can decapsulate.
   * 
   * Real FIPS 203 ML-KEM-768 encapsulation (via @noble/post-quantum).
   */
  encapsulate(recipientPublicKey: Buffer): PQCEncapsulation {
    if (recipientPublicKey.length !== ML_KEM_768.publicKeySize) {
      throw new Error(`Invalid public key size: expected ${ML_KEM_768.publicKeySize}, got ${recipientPublicKey.length}`);
    }

    // Real ML-KEM-768 encapsulation (FIPS 203) via @noble/post-quantum.
    // encapsulate() draws a fresh 32-byte message m from the platform CSPRNG and
    // derives the ciphertext (1088 bytes) + shared secret (32 bytes) by lattice math.
    const enc = ml_kem768.encapsulate(recipientPublicKey);
    const ciphertext = Buffer.from(enc.cipherText);   // 1088 bytes (c)
    const sharedSecret = Buffer.from(enc.sharedSecret); // 32 bytes (K)

    pqcLogger.debug('Encapsulated shared secret', {
      metadata: {
        sharedSecretBytes: sharedSecret.length,
        ciphertextBytes: ciphertext.length
      }
    });

    return {
      ciphertext,
      sharedSecret,
      algorithm: ML_KEM_768.name,
      timestamp: new Date()
    };
  }

  /**
   * Decapsulates a ciphertext using our secret key to recover the shared secret.
   * 
   * Real FIPS 203 ML-KEM-768 decapsulation (via @noble/post-quantum).
   */
  decapsulate(ciphertext: Buffer): Buffer {
    if (!this.keyPair) {
      throw new Error('No key pair generated. Call generateKeyPair() first.');
    }

    if (ciphertext.length !== ML_KEM_768.ciphertextSize) {
      throw new Error(`Invalid ciphertext size: expected ${ML_KEM_768.ciphertextSize}, got ${ciphertext.length}`);
    }

    // Real ML-KEM-768 decapsulation (FIPS 203) via @noble/post-quantum.
    // Recovers the shared secret from the ciphertext using our secret key (dk).
    // On a tampered/invalid ciphertext this performs implicit rejection (returns a
    // deterministic pseudo-random 32-byte secret derived from dk's z value) rather
    // than throwing — matching FIPS 203 §6.3.
    const sharedSecret = Buffer.from(
      ml_kem768.decapsulate(ciphertext, this.keyPair.secretKey)
    ); // 32 bytes (K')

    pqcLogger.debug('Decapsulated shared secret', { metadata: { sharedSecretBytes: sharedSecret.length } });

    return sharedSecret;
  }

  /**
   * Establishes a quantum-resistant session with a terminal.
   * Used during S512 (PQC Handshake) in the failover sequence.
   */
  establishSession(terminalId: string, terminalPublicKey: Buffer): PQCSession {
    const encapsulation = this.encapsulate(terminalPublicKey);
    
    const sessionId = crypto.randomBytes(16).toString('hex');
    const now = new Date();
    const expiresAt = new Date(now.getTime() + 12 * 60 * 60 * 1000); // 12 hours

    const session: PQCSession = {
      sessionId,
      nodeId: terminalId,
      sharedSecret: encapsulation.sharedSecret,
      establishedAt: now,
      expiresAt,
      messageCount: 0
    };

    this.sessions.set(terminalId, session);
    
    pqcLogger.info('Session established with terminal', {
      metadata: { terminalId, sessionId, expiresAt: expiresAt.toISOString() }
    });

    return session;
  }

  /**
   * Signs a transaction using the session's shared secret.
   * This creates a quantum-resistant authentication tag.
   */
  signTransaction(terminalId: string, transactionData: string): PQCSignature {
    const session = this.sessions.get(terminalId);
    if (!session) {
      throw new Error(`No session found for terminal ${terminalId}`);
    }

    if (new Date() > session.expiresAt) {
      throw new Error(`Session expired for terminal ${terminalId}`);
    }

    // HMAC-SHA256 with shared secret (quantum-resistant when key is PQC-derived)
    const signature = crypto
      .createHmac('sha256', session.sharedSecret)
      .update(transactionData)
      .update(session.messageCount.toString())
      .digest();

    session.messageCount++;

    return {
      signature,
      algorithm: 'HMAC-SHA256-PQC',
      timestamp: new Date(),
      nodeId: this.nodeId
    };
  }

  /**
   * Verifies a transaction signature.
   */
  verifySignature(terminalId: string, transactionData: string, signature: Buffer, messageCount: number): boolean {
    const session = this.sessions.get(terminalId);
    if (!session) {
      return false;
    }

    const expected = crypto
      .createHmac('sha256', session.sharedSecret)
      .update(transactionData)
      .update(messageCount.toString())
      .digest();

    return crypto.timingSafeEqual(signature, expected);
  }

  /**
   * Encrypts data using the session key (AES-256-GCM with PQC-derived key).
   */
  encryptWithSession(terminalId: string, plaintext: Buffer): { ciphertext: Buffer; iv: Buffer; tag: Buffer } {
    const session = this.sessions.get(terminalId);
    if (!session) {
      throw new Error(`No session found for terminal ${terminalId}`);
    }

    const iv = crypto.randomBytes(12);
    const cipher = crypto.createCipheriv('aes-256-gcm', session.sharedSecret, iv);
    
    const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    const tag = cipher.getAuthTag();

    return { ciphertext: encrypted, iv, tag };
  }

  /**
   * Decrypts data using the session key.
   */
  decryptWithSession(terminalId: string, ciphertext: Buffer, iv: Buffer, tag: Buffer): Buffer {
    const session = this.sessions.get(terminalId);
    if (!session) {
      throw new Error(`No session found for terminal ${terminalId}`);
    }

    const decipher = crypto.createDecipheriv('aes-256-gcm', session.sharedSecret, iv, { authTagLength: 16 });
    decipher.setAuthTag(tag);
    
    return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
  }

  /**
   * Returns the public key for sharing with terminals.
   */
  getPublicKey(): Buffer {
    if (!this.keyPair) {
      throw new Error('No key pair generated. Call generateKeyPair() first.');
    }
    return this.keyPair.publicKey;
  }

  /**
   * Returns the key fingerprint for identification.
   */
  getFingerprint(): string {
    if (!this.keyPair) {
      throw new Error('No key pair generated. Call generateKeyPair() first.');
    }
    return this.keyPair.fingerprint;
  }

  /**
   * Returns session status for monitoring.
   */
  getSessionStatus(): { activeCount: number; sessions: Array<{ nodeId: string; expiresAt: Date; messageCount: number }> } {
    const sessions = Array.from(this.sessions.values()).map(s => ({
      nodeId: s.nodeId,
      expiresAt: s.expiresAt,
      messageCount: s.messageCount
    }));

    return {
      activeCount: sessions.length,
      sessions
    };
  }

  /**
   * Cleans up expired sessions.
   */
  cleanupExpiredSessions(): number {
    const now = new Date();
    let removed = 0;

    const entries = Array.from(this.sessions.entries());
    for (const [terminalId, session] of entries) {
      if (now > session.expiresAt) {
        this.sessions.delete(terminalId);
        removed++;
      }
    }

    if (removed > 0) {
      pqcLogger.debug('Cleaned up expired sessions', { metadata: { removed } });
    }

    return removed;
  }

  /**
   * Returns algorithm information for audit/compliance.
   */
  getAlgorithmInfo(): typeof ML_KEM_768 {
    return { ...ML_KEM_768 };
  }
}

// Singleton instance
let pqcEngineInstance: MLKEMEngine | null = null;

export function getPQCEngine(nodeId: string = 'edge-node-001'): MLKEMEngine {
  if (!pqcEngineInstance) {
    pqcEngineInstance = new MLKEMEngine(nodeId);
    pqcEngineInstance.generateKeyPair();
  }
  return pqcEngineInstance;
}

/**
 * Convenience function for PQC handshake during sovereign mode transition.
 */
export function performPQCHandshake(
  terminalId: string,
  terminalPublicKey: Buffer,
  nodeId: string = 'edge-node-001'
): { session: PQCSession; edgePublicKey: Buffer } {
  const engine = getPQCEngine(nodeId);
  const session = engine.establishSession(terminalId, terminalPublicKey);
  
  return {
    session,
    edgePublicKey: engine.getPublicKey()
  };
}
