//-----------------------------------------------------------------------
// <copyright file="SrpArgonEncryptionTests.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------

namespace AliasVault.UnitTests.Utilities;

using System.Security.Cryptography;
using AliasVault.Cryptography.Client;
using SecureRemotePassword;

/// <summary>
/// Tests for the SrpArgonEncryption class.
/// </summary>
public class SrpArgonEncryptionTests
{
    /// <summary>
    /// Test basic encryption and decryption using default encryption logic (Argon2id and AES-256).
    /// </summary>
    /// <returns>Task.</returns>
    [Test]
    public async Task TestBasicEncrypt()
    {
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        string password = "your-password";
        string salt = "your-salt"; // Use a secure random salt in production

        string plaintext = "Hello, World!";

        // Derive a key from the password using Argon2id
        byte[] key = await Encryption.DeriveKeyFromPasswordAsync(password, salt);
        Console.WriteLine($"Derived key: {key.Length} bytes (hex: {Convert.ToHexString(key).Replace("-", string.Empty)})");

        // SymmetricEncrypt the plaintext
        string encrypted = Cryptography.Server.Encryption.SymmetricEncrypt(plaintext, key);
        Console.WriteLine($"Encrypted: {encrypted}");

        Assert.That(encrypted, Is.Not.Null);
        Assert.That(encrypted, Is.Not.Empty);
        Assert.That(encrypted, Is.Not.EqualTo(plaintext));

        // SymmetricDecrypt the ciphertext
        string decrypted = Cryptography.Server.Encryption.SymmetricDecrypt(encrypted, key);
        Console.WriteLine($"Decrypted: {decrypted}");
        Assert.That(decrypted, Is.EqualTo(plaintext));

        stopwatch.Stop();
        var elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
        Console.WriteLine($"Execution time: {elapsedMilliseconds} ms");
    }

    /// <summary>
    /// Test that the used JS encryption implementation is compatible with the C# encryption implementation.
    /// </summary>
    [Test]
    public void TestJsEncryption()
    {
        string jsEncryptionKeyBase64 = "vtIsIn3D9oZcQ2ssfXLwM6EduYbW3b1tFSZPzmhhy+Y=";
        string jsEncryptedBase64Contents = "[encoded data, 21884 characters]";
        var encryptionKey = Convert.FromBase64String(jsEncryptionKeyBase64);

        // Try to decrypt it from .NET.
        var decrypted = Cryptography.Server.Encryption.SymmetricDecrypt(jsEncryptedBase64Contents, encryptionKey);

        // Assert that its equal as the original what we expect.
        var originalUnencrypted = "[encoded data, 16384 characters]";
        Assert.That(decrypted, Is.EqualTo(originalUnencrypted));
    }

    /// <summary>
    /// Test basic encryption and decryption using default encryption logic (Argon2id and AES-256).
    /// </summary>
    /// <returns>Task.</returns>
    [Test]
    public async Task TestNotEqualsPassword()
    {
        string password = "your-password";
        string salt = "your-salt"; // Use a secure random salt in production

        string plaintext = "Hello, World!";

        // Derive a key from the password using Argon2id
        byte[] key = await Cryptography.Client.Encryption.DeriveKeyFromPasswordAsync(password, salt);

        // SymmetricEncrypt the plaintext
        string encrypted = Cryptography.Server.Encryption.SymmetricEncrypt(plaintext, key);

        // SymmetricDecrypt the ciphertext using a different key
        byte[] key2 = await Cryptography.Client.Encryption.DeriveKeyFromPasswordAsync("your-password2", salt);

        Assert.Throws<AuthenticationTagMismatchException>(() => Cryptography.Server.Encryption.SymmetricDecrypt(encrypted, key2));
    }

    /// <summary>
    /// Test the SRP authentication flow to ensure it works correctly.
    /// </summary>
    /// <returns>Async task.</returns>
    [Test]
    public async Task TestSrpAuthentication()
    {
        var email = "test@example.com";
        var password = "myPassword";

        // Signup -----------------------------
        // Client generates a salt and verifier.
        var client = new SrpClient();
        var salt = client.GenerateSalt();

        byte[] passwordHash = await Cryptography.Client.Encryption.DeriveKeyFromPasswordAsync(password, salt);
        var passwordHashString = Convert.ToHexString(passwordHash).Replace("-", string.Empty);
        var srpSignup = Srp.PasswordChangeAsync(client, salt, email, passwordHashString);

        var privateKey = srpSignup.PrivateKey;
        var verifier = srpSignup.Verifier;

        // Login -----------------------------------
        // 1. Client generates an ephemeral value.
        var clientEphemeral = Srp.GenerateEphemeralClient();

        // --> Then client sends request to server.

        // 2. Server retrieves salt and verifier from database.
        // Then server generates an ephemeral value as well.
        var serverEphemeral = Srp.GenerateEphemeralServer(verifier);

        // --> Send serverEphemeral.Public to client.

        // 3. Client derives shared session key.
        var clientSession = Srp.DeriveSessionClient(privateKey, clientEphemeral.Secret, serverEphemeral.Public, salt, email);

        // --> send session.Proof to server.

        // 4. Server verifies the proof.
        var serverSession = Srp.DeriveSessionServer(serverEphemeral.Secret, clientEphemeral.Public, salt, email, verifier, clientSession.Proof);

        // --> send serverSession.Proof to client.
        Assert.That(serverSession, Is.Not.Null, "Server session is null, likely indicating that the provided password is incorrect.");

        // 5. Client verifies the proof.
        Srp.VerifySession(clientEphemeral.Public, clientSession, serverSession.Proof);

        // Ensure the keys match.
        Assert.That(clientSession.Key, Is.EqualTo(serverSession.Key));
    }

    /// <summary>
    /// Test byte array encryption and decryption overload methods.
    /// </summary>
    /// <returns>Task.</returns>
    [Test]
    public async Task TestByteArrayEncryption()
    {
        string password = "your-password";
        string salt = "your-salt";

        // Derive a key from the password using Argon2id
        byte[] key = await Cryptography.Client.Encryption.DeriveKeyFromPasswordAsync(password, salt);

        // Generate random byte array of 1000 bytes
        byte[] plainBytes = new byte[1000];
        new Random().NextBytes(plainBytes);

        // SymmetricEncrypt the plainBytes
        var cipherBytes = Cryptography.Server.Encryption.SymmetricEncrypt(plainBytes, key);

        Assert.That(cipherBytes, Is.Not.Null);
        Assert.That(cipherBytes, Is.Not.Empty);
        Assert.That(cipherBytes, Is.Not.EqualTo(plainBytes));

        // SymmetricDecrypt the cipherBytes
        var decrypted = Cryptography.Server.Encryption.SymmetricDecrypt(cipherBytes, key);
        Assert.That(decrypted, Is.EqualTo(plainBytes));
    }
}
