// Test file for PQXDH protocol (RFC-0830)
// Located at: l1-identity/test_pqxdh.zig
//
// This file tests the PQXDH key agreement ceremony with stubbed ML-KEM functions.
// Once liboqs is built, these tests will use real ML-KEM-768 implementation.

const std = @import("std");
const pqxdh = @import("pqxdh.zig");
const testing = std.testing;

// ============================================================================
// Real LibOQS Functions (via C Import)
// ============================================================================

const c = @cImport({
    @cInclude("oqs/oqs.h");
});

// ============================================================================
// Helper: Generate Test Keypairs
// ============================================================================

fn generateTestKeypair() ![32]u8 {
    var private_key: [32]u8 = undefined;
    std.crypto.random.bytes(&private_key);
    return private_key;
}

// ============================================================================
// Tests
// ============================================================================

test "PQXDHPrekeyBundle serialization roundtrip" {
    const allocator = testing.allocator;

    var bundle = pqxdh.PrekeyBundle{
        .identity_key = [_]u8{0x01} ** 32,
        .signed_prekey_x25519 = [_]u8{0x02} ** 32,
        .signed_prekey_signature = [_]u8{0x03} ** 64,
        .signed_prekey_mlkem = [_]u8{0x04} ** pqxdh.ML_KEM_768.PUBLIC_KEY_SIZE,
        .one_time_prekey_x25519 = [_]u8{0x05} ** 32,
        .one_time_prekey_mlkem = [_]u8{0x06} ** pqxdh.ML_KEM_768.PUBLIC_KEY_SIZE,
    };

    // Serialize
    const bytes = try bundle.toBytes(allocator);
    defer allocator.free(bytes);

    // Expected size: 32 + 32 + 64 + 1184 + 32 + 1184 = 2528 bytes
    try testing.expectEqual(@as(usize, 2528), bytes.len);

    // Deserialize
    const restored = try pqxdh.PrekeyBundle.fromBytes(allocator, bytes);

    // Verify all fields match
    try testing.expectEqualSlices(u8, &bundle.identity_key, &restored.identity_key);
    try testing.expectEqualSlices(u8, &bundle.signed_prekey_x25519, &restored.signed_prekey_x25519);
    try testing.expectEqualSlices(u8, &bundle.signed_prekey_signature, &restored.signed_prekey_signature);
    try testing.expectEqualSlices(u8, &bundle.signed_prekey_mlkem, &restored.signed_prekey_mlkem);
    try testing.expectEqualSlices(u8, &bundle.one_time_prekey_x25519, &restored.one_time_prekey_x25519);
    try testing.expectEqualSlices(u8, &bundle.one_time_prekey_mlkem, &restored.one_time_prekey_mlkem);
}

test "PQXDHInitialMessage serialization roundtrip" {
    const allocator = testing.allocator;

    var msg = pqxdh.PQXDHInitialMessage{
        .ephemeral_x25519 = [_]u8{0x11} ** 32,
        .mlkem_ciphertext = [_]u8{0x22} ** pqxdh.ML_KEM_768.CIPHERTEXT_SIZE,
    };

    // Serialize
    const bytes = try msg.toBytes(allocator);
    defer allocator.free(bytes);

    // Expected size: 32 + 1088 = 1120 bytes
    try testing.expectEqual(@as(usize, 1120), bytes.len);

    // Deserialize
    const restored = try pqxdh.PQXDHInitialMessage.fromBytes(bytes);

    // Verify fields match
    try testing.expectEqualSlices(u8, &msg.ephemeral_x25519, &restored.ephemeral_x25519);
    try testing.expectEqualSlices(u8, &msg.mlkem_ciphertext, &restored.mlkem_ciphertext);
}

test "PQXDH full handshake roundtrip (real ML-KEM)" {
    const allocator = testing.allocator;

    // === Bob's Setup ===
    // Generate Bob's long-term identity key (Ed25519 → X25519 conversion)
    const bob_identity_private = try generateTestKeypair();
    const bob_identity_public = try std.crypto.dh.X25519.recoverPublicKey(bob_identity_private);

    // Generate Bob's signed prekey (X25519)
    const bob_signed_prekey_private = try generateTestKeypair();
    const bob_signed_prekey_public = try std.crypto.dh.X25519.recoverPublicKey(bob_signed_prekey_private);

    // Generate Bob's one-time prekey (X25519)
    const bob_onetime_prekey_private = try generateTestKeypair();
    const bob_onetime_prekey_public = try std.crypto.dh.X25519.recoverPublicKey(bob_onetime_prekey_private);

    // Generate Bob's ML-KEM keypair (stubbed)
    var bob_mlkem_public: [pqxdh.ML_KEM_768.PUBLIC_KEY_SIZE]u8 = undefined;
    var bob_mlkem_private: [pqxdh.ML_KEM_768.SECRET_KEY_SIZE]u8 = undefined;
    const kem_result = c.OQS_KEM_ml_kem_768_keypair(&bob_mlkem_public[0], &bob_mlkem_private[0]);
    try testing.expectEqual(@as(c_int, 0), kem_result);

    // Create Bob's prekey bundle (signature stubbed for now)
    var bob_bundle = pqxdh.PrekeyBundle{
        .identity_key = bob_identity_public,
        .signed_prekey_x25519 = bob_signed_prekey_public,
        .signed_prekey_signature = [_]u8{0} ** 64, // TODO: Real Ed25519 signature
        .signed_prekey_mlkem = bob_mlkem_public,
        .one_time_prekey_x25519 = bob_onetime_prekey_public,
        .one_time_prekey_mlkem = bob_mlkem_public, // Reuse for test
    };

    // === Alice's Setup ===
    const alice_identity_private = try generateTestKeypair();
    const alice_identity_public = try std.crypto.dh.X25519.recoverPublicKey(alice_identity_private);

    // === Alice Initiates Handshake ===
    const alice_result = try pqxdh.initiator(
        alice_identity_private,
        &bob_bundle,
        allocator,
    );

    // Verify Alice got a root key
    var alice_has_nonzero = false;
    for (alice_result.root_key) |byte| {
        if (byte != 0) {
            alice_has_nonzero = true;
            break;
        }
    }
    try testing.expect(alice_has_nonzero);

    // === Bob Responds to Handshake ===
    const bob_result = try pqxdh.responder(
        bob_identity_private,
        bob_signed_prekey_private,
        bob_onetime_prekey_private,
        bob_mlkem_private,
        alice_identity_public,
        &alice_result.initial_message,
    );

    // === Verify Root Keys Match ===
    // This is the critical test: both parties must derive the SAME root key
    try testing.expectEqualSlices(u8, &alice_result.root_key, &bob_result.root_key);

    std.debug.print("\n✅ PQXDH Handshake: Alice and Bob derived matching root keys!\n", .{});
    std.debug.print("   Root key (first 16 bytes): {x}\n", .{alice_result.root_key[0..16]});
}

test "PQXDH error: invalid ML-KEM encapsulation" {
    // Test that errors propagate correctly when ML-KEM fails
    // (This test will be more meaningful with real liboqs)

    // For now, just verify our stub functions return success
    var public_key: [pqxdh.ML_KEM_768.PUBLIC_KEY_SIZE]u8 = undefined;
    var secret_key: [pqxdh.ML_KEM_768.SECRET_KEY_SIZE]u8 = undefined;

    const result = c.OQS_KEM_ml_kem_768_keypair(&public_key[0], &secret_key[0]);
    try testing.expectEqual(@as(c_int, 0), result);
}
