using System.Security.Cryptography;
using ZB.MOM.WW.Secrets.Abstractions;
using ZB.MOM.WW.Secrets.MasterKey;

namespace ZB.MOM.WW.Secrets.Tests.MasterKey;

[Collection("Process environment")]
public class MasterKeyProviderTests
{
    // ---- Environment ----------------------------------------------------------------------

    [Fact]
    public void Environment_ReturnsDecodedKey()
    {
        byte[] key = RandomNumberGenerator.GetBytes(32);
        string envVar = UniqueEnvVarName();
        Environment.SetEnvironmentVariable(envVar, Convert.ToBase64String(key));
        try
        {
            var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });

            Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
        }
        finally
        {
            Environment.SetEnvironmentVariable(envVar, null);
        }
    }

    [Fact]
    public void Environment_KekId_IsStableAcrossCalls()
    {
        byte[] key = RandomNumberGenerator.GetBytes(32);
        string envVar = UniqueEnvVarName();
        Environment.SetEnvironmentVariable(envVar, Convert.ToBase64String(key));
        try
        {
            var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });

            string first = provider.KekId;
            string second = provider.KekId;

            Assert.Equal(first, second);
            Assert.StartsWith("sha256:", first, StringComparison.Ordinal);
        }
        finally
        {
            Environment.SetEnvironmentVariable(envVar, null);
        }
    }

    [Fact]
    public void Environment_ShortKey_Throws()
    {
        byte[] shortKey = RandomNumberGenerator.GetBytes(16);
        string envVar = UniqueEnvVarName();
        Environment.SetEnvironmentVariable(envVar, Convert.ToBase64String(shortKey));
        try
        {
            var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });

            Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
        }
        finally
        {
            Environment.SetEnvironmentVariable(envVar, null);
        }
    }

    [Fact]
    public void Environment_MissingVariable_Throws()
    {
        string envVar = UniqueEnvVarName();
        Environment.SetEnvironmentVariable(envVar, null); // ensure absent
        var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });

        Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
    }

    // ---- File -----------------------------------------------------------------------------

    [Fact]
    public void File_Raw32Bytes_ReturnsKey()
    {
        byte[] key = RandomNumberGenerator.GetBytes(32);
        string path = Path.GetTempFileName();
        File.WriteAllBytes(path, key);
        try
        {
            var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });

            Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
        }
        finally
        {
            File.Delete(path);
        }
    }

    [Fact]
    public void File_Base64Text_ReturnsKey()
    {
        byte[] key = RandomNumberGenerator.GetBytes(32);
        string path = Path.GetTempFileName();
        File.WriteAllText(path, Convert.ToBase64String(key) + "\n");
        try
        {
            var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });

            Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
        }
        finally
        {
            File.Delete(path);
        }
    }

    [Fact]
    public void File_TenBytes_Throws()
    {
        // 10 raw bytes: not 32, and not valid base64 text either → fail closed.
        string path = Path.GetTempFileName();
        File.WriteAllBytes(path, RandomNumberGenerator.GetBytes(10));
        try
        {
            var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });

            Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
        }
        finally
        {
            File.Delete(path);
        }
    }

    [Fact]
    public void File_MissingFile_Throws()
    {
        var provider = new FileMasterKeyProvider(
            new MasterKeyOptions { FilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")) });

        Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
    }

    // ---- KekId derivation -----------------------------------------------------------------

    [Fact]
    public void ExplicitKekId_IsUsedVerbatim()
    {
        byte[] key = RandomNumberGenerator.GetBytes(32);
        string path = Path.GetTempFileName();
        File.WriteAllBytes(path, key);
        try
        {
            var provider = new FileMasterKeyProvider(
                new MasterKeyOptions { FilePath = path, KekId = "prod-kek-2026" });

            Assert.Equal("prod-kek-2026", provider.KekId);
        }
        finally
        {
            File.Delete(path);
        }
    }

    [Fact]
    public void DerivedKekId_StartsWithSha256()
    {
        byte[] key = RandomNumberGenerator.GetBytes(32);
        string path = Path.GetTempFileName();
        File.WriteAllBytes(path, key);
        try
        {
            var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });

            Assert.StartsWith("sha256:", provider.KekId, StringComparison.Ordinal);
        }
        finally
        {
            File.Delete(path);
        }
    }

    // ---- Factory --------------------------------------------------------------------------

    [Fact]
    public void Factory_Environment_ReturnsEnvironmentProvider()
    {
        IMasterKeyProvider provider = MasterKeyProviderFactory.Create(
            new MasterKeyOptions { Source = MasterKeySource.Environment });

        Assert.IsType<EnvironmentMasterKeyProvider>(provider);
    }

    [Fact]
    public void Factory_File_ReturnsFileProvider()
    {
        IMasterKeyProvider provider = MasterKeyProviderFactory.Create(
            new MasterKeyOptions { Source = MasterKeySource.File, FilePath = "unused" });

        Assert.IsType<FileMasterKeyProvider>(provider);
    }

    // ---- DPAPI (Windows-only) -------------------------------------------------------------

    [SkippableFact]
    public void Dpapi_RoundTripsOnWindows()
    {
        Skip.IfNot(OperatingSystem.IsWindows(), "DPAPI is only supported on Windows.");

        byte[] key = RandomNumberGenerator.GetBytes(32);
#pragma warning disable CA1416 // Whole body is guarded by Skip.IfNot(IsWindows) above.
        byte[] blob = ProtectedData.Protect(key, optionalEntropy: null, DataProtectionScope.CurrentUser);
        string path = Path.GetTempFileName();
        File.WriteAllBytes(path, blob);
        try
        {
            var provider = new DpapiMasterKeyProvider(new MasterKeyOptions { FilePath = path });

            Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
        }
        finally
        {
            File.Delete(path);
        }
#pragma warning restore CA1416
    }

    private static string UniqueEnvVarName() => "ZB_SECRETS_TEST_" + Guid.NewGuid().ToString("N");
}
