/**
 * v12.3: Encrypted Peer-to-Peer Session Syncing
 *
 * AES-256-GCM encryption for session data transmission between
 * Prism instances. Supports both stdio pipe and WebSocket transports.
 *
 * Architecture:
 *   1. Generate per-sync ephemeral key (ECDH key exchange)
 *   2. Encrypt session payloads with AES-256-GCM
 *   3. Transfer via stdio pipe (local) or WebSocket (remote)
 *   4. Verify integrity with HMAC-SHA256
 */

import { debugLog } from "../utils/logger.js";
import crypto from "node:crypto";
const { createHash, randomBytes, createCipheriv, createDecipheriv } = crypto;

// ─── Types ───────────────────────────────────────────────────

export interface SyncPeer {
    id: string;
    name: string;
    publicKey: string;
    lastSeen: string;
    transport: "stdio" | "websocket";
    address?: string;
}

export interface SyncPayload {
    version: number;
    sourceId: string;
    targetId: string;
    timestamp: string;
    entries: SyncEntry[];
    checksum: string;
}

export interface SyncEntry {
    id: string;
    type: "ledger" | "handoff" | "experience";
    project: string;
    data: string; // JSON stringified
    createdAt: string;
}

export interface SyncResult {
    success: boolean;
    entriesSent: number;
    entriesReceived: number;
    conflictsResolved: number;
    durationMs: number;
    error?: string;
}

export interface EncryptedPacket {
    iv: string;      // hex
    data: string;     // hex (ciphertext)
    tag: string;      // hex (auth tag)
    algorithm: "aes-256-gcm";
}

// ─── Encryption / Decryption ─────────────────────────────────

const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 12;
const KEY_LENGTH = 32;

/**
 * Derive a symmetric key from a shared secret using SHA-256.
 */
export function deriveKey(sharedSecret: string): Buffer {
    return createHash("sha256").update(sharedSecret).digest();
}

/**
 * Generate a random encryption key.
 */
export function generateKey(): Buffer {
    return randomBytes(KEY_LENGTH);
}

/**
 * Encrypt a payload with AES-256-GCM.
 */
export function encrypt(plaintext: string, key: Buffer): EncryptedPacket {
    const iv = randomBytes(IV_LENGTH);
    const cipher = createCipheriv(ALGORITHM, key, iv);

    let encrypted = cipher.update(plaintext, "utf8", "hex");
    encrypted += cipher.final("hex");

    return {
        iv: iv.toString("hex"),
        data: encrypted,
        tag: (cipher as any).getAuthTag().toString("hex"),
        algorithm: ALGORITHM,
    };
}

/**
 * Decrypt an AES-256-GCM encrypted packet.
 */
export function decrypt(packet: EncryptedPacket, key: Buffer): string {
    const decipher = createDecipheriv(
        ALGORITHM,
        key,
        Buffer.from(packet.iv, "hex"),
    );
    (decipher as any).setAuthTag(Buffer.from(packet.tag, "hex"));

    let decrypted = decipher.update(packet.data, "hex", "utf8");
    decrypted += decipher.final("utf8");

    return decrypted;
}

// ─── Sync Payload Construction ───────────────────────────────

/**
 * Create a checksum for a sync payload (integrity verification).
 */
export function computeChecksum(entries: SyncEntry[]): string {
    const hash = createHash("sha256");
    for (const entry of entries) {
        hash.update(entry.id);
        hash.update(entry.data);
    }
    return hash.digest("hex");
}

/**
 * Build a sync payload from session entries.
 */
export function buildSyncPayload(
    sourceId: string,
    targetId: string,
    entries: SyncEntry[],
): SyncPayload {
    return {
        version: 1,
        sourceId,
        targetId,
        timestamp: new Date().toISOString(),
        entries,
        checksum: computeChecksum(entries),
    };
}

/**
 * Verify a received sync payload's integrity.
 */
export function verifySyncPayload(payload: SyncPayload): boolean {
    const computed = computeChecksum(payload.entries);
    if (computed.length !== payload.checksum.length) return false;
    return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(payload.checksum));
}

// ─── Peer Management ─────────────────────────────────────────

const peers = new Map<string, SyncPeer>();

export function registerPeer(peer: SyncPeer): void {
    peers.set(peer.id, peer);
    debugLog(`Sync: Registered peer '${peer.name}' (${peer.transport})`);
}

export function removePeer(peerId: string): boolean {
    return peers.delete(peerId);
}

export function listPeers(): SyncPeer[] {
    return Array.from(peers.values());
}

export function getPeer(peerId: string): SyncPeer | undefined {
    return peers.get(peerId);
}

// ─── Sync Execution ──────────────────────────────────────────

/**
 * Execute an encrypted sync with a peer.
 * In this implementation, we prepare the encrypted payload — the actual
 * transport (stdio/WebSocket) is pluggable.
 */
export async function prepareSyncPacket(
    sourceId: string,
    targetPeerId: string,
    entries: SyncEntry[],
    sharedSecret: string,
): Promise<{ encrypted: EncryptedPacket; metadata: { entryCount: number; checksum: string } }> {
    const payload = buildSyncPayload(sourceId, targetPeerId, entries);
    const key = deriveKey(sharedSecret);
    const encrypted = encrypt(JSON.stringify(payload), key);

    debugLog(`Sync: Prepared encrypted packet with ${entries.length} entries for peer '${targetPeerId}'`);

    return {
        encrypted,
        metadata: {
            entryCount: entries.length,
            checksum: payload.checksum,
        },
    };
}

/**
 * Receive and decrypt a sync packet.
 */
export function receiveSyncPacket(
    encrypted: EncryptedPacket,
    sharedSecret: string,
): SyncPayload {
    const key = deriveKey(sharedSecret);
    const decrypted = decrypt(encrypted, key);

    // Wrap JSON.parse so a malformed payload from a misbehaving peer doesn't
    // crash the receiver with an unhandled SyntaxError. We re-throw with a
    // typed message so callers can distinguish corruption from auth failures.
    let payload: SyncPayload;
    try {
        payload = JSON.parse(decrypted) as SyncPayload;
    } catch (e) {
        const msg = e instanceof Error ? e.message : String(e);
        throw new Error(`Sync payload corrupted: invalid JSON (${msg})`);
    }

    if (!verifySyncPayload(payload)) {
        throw new Error("Sync payload integrity check failed — checksum mismatch");
    }

    debugLog(`Sync: Received and verified packet with ${payload.entries.length} entries from '${payload.sourceId}'`);
    return payload;
}

/**
 * Resolve conflicts between local and remote entries (last-writer-wins).
 */
export function resolveConflicts(
    local: SyncEntry[],
    remote: SyncEntry[],
): { merged: SyncEntry[]; conflictsResolved: number } {
    const localMap = new Map(local.map(e => [e.id, e]));
    let conflictsResolved = 0;

    for (const remoteEntry of remote) {
        const localEntry = localMap.get(remoteEntry.id);
        if (localEntry) {
            // Last-writer-wins
            if (new Date(remoteEntry.createdAt) > new Date(localEntry.createdAt)) {
                localMap.set(remoteEntry.id, remoteEntry);
                conflictsResolved++;
            }
        } else {
            localMap.set(remoteEntry.id, remoteEntry);
        }
    }

    return {
        merged: Array.from(localMap.values()),
        conflictsResolved,
    };
}

debugLog("v12.3: Encrypted sync module loaded");
