using System.Security.Cryptography;
using NSec.Cryptography;

namespace DodoSSH.Crypto.Tests;

/// <summary>
/// Proves the primitives docs/crypto.md depends on are actually available and functional on
/// this runtime and platform.
/// </summary>
/// <remarks>
/// Not ceremonial. Two concrete risks motivated these:
/// the BCL has no X25519 or Ed25519 at all, and <c>ChaCha20Poly1305.IsSupported</c> is false
/// on macOS, which is what disqualified the in-box AEAD for a cross-platform client. If any
/// of these fail on a target platform, the specification is wrong rather than the code.
/// </remarks>
public sealed class PrimitiveAvailabilityTests
{
    [Fact]
    public void X25519_AgreesOnASharedSecret()
    {
        var algorithm = KeyAgreementAlgorithm.X25519;
        var creation = new KeyCreationParameters { ExportPolicy = KeyExportPolicies.AllowPlaintextExport };

        using var alice = Key.Create(algorithm, creation);
        using var bob = Key.Create(algorithm, creation);

        using var aliceView = algorithm.Agree(alice, bob.PublicKey)!;
        using var bobView = algorithm.Agree(bob, alice.PublicKey)!;

        var derive = KeyDerivationAlgorithm.HkdfSha256;
        var fromAlice = derive.DeriveBytes(aliceView, ReadOnlySpan<byte>.Empty, "test"u8, 32);
        var fromBob = derive.DeriveBytes(bobView, ReadOnlySpan<byte>.Empty, "test"u8, 32);

        fromAlice.ShouldBe(fromBob);
    }

    [Fact]
    public void Ed25519_SignsAndVerifies()
    {
        var algorithm = SignatureAlgorithm.Ed25519;
        using var signer = Key.Create(algorithm);

        var message = "grant tuple"u8;
        var signature = algorithm.Sign(signer, message);

        signature.Length.ShouldBe(64);
        algorithm.Verify(signer.PublicKey, message, signature).ShouldBeTrue();
        algorithm.Verify(signer.PublicKey, "tampered"u8, signature).ShouldBeFalse();
    }

    [Fact]
    public void XChaCha20Poly1305_RoundTripsAndDetectsAadTampering()
    {
        var algorithm = AeadAlgorithm.XChaCha20Poly1305;
        using var key = Key.Create(algorithm);

        var nonce = RandomNumberGenerator.GetBytes(algorithm.NonceSize);
        var plaintext = "id_ed25519 private key"u8;

        var ciphertext = algorithm.Encrypt(key, nonce, "aad-a"u8, plaintext);

        algorithm.Decrypt(key, nonce, "aad-a"u8, ciphertext).ShouldBe(plaintext.ToArray());

        // The whole point of binding AAD to row identity: a different AAD must not decrypt.
        algorithm.Decrypt(key, nonce, "aad-b"u8, ciphertext).ShouldBeNull();
    }

    [Fact]
    public void XChaCha20Poly1305_NonceIs24BytesSoRandomNoncesAreSafe()
    {
        // 192-bit nonces are why we can generate one at random per message without tracking
        // a counter. AES-GCM's 96-bit nonce would not permit that.
        AeadAlgorithm.XChaCha20Poly1305.NonceSize.ShouldBe(24);
        AeadAlgorithm.XChaCha20Poly1305.KeySize.ShouldBe(32);
        AeadAlgorithm.XChaCha20Poly1305.TagSize.ShouldBe(16);
    }

    [Fact]
    public void Argon2id_IsAvailableAndParallelismIsPinnedToOne()
    {
        // libsodium's Argon2id implementation only supports p=1. docs/crypto.md compensates
        // with memory cost instead; this test pins the constraint so it is not forgotten.
        var algorithm = PasswordBasedKeyDerivationAlgorithm.Argon2id(
            new Argon2Parameters { DegreeOfParallelism = 1, MemorySize = 1 << 20, NumberOfPasses = 1 });

        var salt = new byte[16];
        var derived = algorithm.DeriveBytes("correct horse battery staple", salt, 32);

        derived.Length.ShouldBe(32);
        derived.ShouldNotBe(new byte[32]);
    }

    [Fact]
    public void Argon2id_IsDeterministicForTheSamePassphraseAndSalt()
    {
        var algorithm = PasswordBasedKeyDerivationAlgorithm.Argon2id(
            new Argon2Parameters { DegreeOfParallelism = 1, MemorySize = 1 << 20, NumberOfPasses = 1 });

        var salt = RandomNumberGenerator.GetBytes(16);

        algorithm.DeriveBytes("passphrase", salt, 32)
            .ShouldBe(algorithm.DeriveBytes("passphrase", salt, 32));
    }

    [Fact]
    public void HkdfSha512_IsAvailableInTheBcl()
    {
        // Subkey derivation from the master key uses the BCL, not NSec: HKDF is fully
        // supported on every platform.
        var info = "dsh1/kek/passphrase/v1"u8.ToArray();
        var okm = HKDF.Expand(HashAlgorithmName.SHA512, new byte[64], 32, info);

        okm.Length.ShouldBe(32);
    }
}
