using MBN_STOCK_WEBVIEW.Playout.Diagnostics;
using MBN_STOCK_WEBVIEW.Playout.Interop;
using MBN_STOCK_WEBVIEW.Playout.Registration;
using System.Security.Cryptography;

namespace MBN_STOCK_WEBVIEW.Playout.Tests;

public sealed class InstalledK3dInteropMetadataTests
{
    [Fact]
    public void ApprovedSha256_RequiresAndDecodesExactly64HexCharacters()
    {
        var parsed = InstalledK3dInteropMetadata.ParseApprovedSha256(
            "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF");

        Assert.Equal(32, parsed.Length);
        Assert.Equal(0x01, parsed[0]);
        Assert.Equal(0xEF, parsed[^1]);
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde")]
    [InlineData(" 123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")]
    [InlineData("g123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")]
    public void ApprovedSha256_RejectsMissingMalformedOrWhitespaceValues(string? value)
    {
        Assert.Throws<InstalledK3dInteropMetadataUnavailableException>(
            () => InstalledK3dInteropMetadata.ParseApprovedSha256(value));
    }

    [Fact]
    public void ApprovedSha256_RequiresExactContentForEitherBinaryPin()
    {
        byte[] content = [1, 2, 3, 4];
        var approved = Convert.ToHexString(SHA256.HashData(content));
        using var matching = new MemoryStream(content);
        using var different = new MemoryStream([1, 2, 3, 5]);

        InstalledK3dInteropMetadata.ValidateApprovedSha256(matching, approved);
        Assert.Throws<InstalledK3dInteropMetadataUnavailableException>(
            () => InstalledK3dInteropMetadata.ValidateApprovedSha256(different, approved));
        Assert.Equal(
            "MBN_STOCK_K3D_NATIVE_SHA256",
            InstalledK3dInteropMetadata.ApprovedNativeSha256EnvironmentVariable);
        Assert.Equal(
            "MBN_STOCK_K3D_INTEROP_SHA256",
            InstalledK3dInteropMetadata.ApprovedSha256EnvironmentVariable);
    }

    [Fact]
    public void FileLease_KeepsNativeAndInteropFilesWriteLocked()
    {
        var directory = Path.Combine(Path.GetTempPath(), $"k3d-lease-{Guid.NewGuid():N}");
        Directory.CreateDirectory(directory);
        var nativePath = Path.Combine(directory, "native.dll");
        var interopPath = Path.Combine(directory, "interop.dll");
        File.WriteAllBytes(nativePath, [1]);
        File.WriteAllBytes(interopPath, [2]);

        InstalledK3dInteropFileLease? lease = null;
        try
        {
            lease = new InstalledK3dInteropFileLease(
                new FileStream(nativePath, FileMode.Open, FileAccess.Read, FileShare.Read),
                new FileStream(interopPath, FileMode.Open, FileAccess.Read, FileShare.Read));

            Assert.Throws<IOException>(() => OpenForWrite(nativePath).Dispose());
            Assert.Throws<IOException>(() => OpenForWrite(interopPath).Dispose());
        }
        finally
        {
            lease?.InteropStream.Dispose();
            lease?.NativeStream.Dispose();
            Directory.Delete(directory, recursive: true);
        }
    }

    [Fact]
    public void RegisteredPaths_MapOnlyExactX64VendorLayout()
    {
        const string native = @"C:\Vendor\K3DAsyncEngine\DLL\x64\Release\K3DAsyncEngine.dll";
        var paths = InstalledK3dInteropMetadata.ResolveRegisteredPaths(ReadySnapshot(native));

        Assert.Equal(Path.GetFullPath(native), paths.NativeBinaryPath);
        Assert.Equal(
            Path.GetFullPath(
                @"C:\Vendor\K3DAsyncEngine\Bin\x64\C#\Interop.K3DAsyncEngineLib.dll"),
            paths.InteropAssemblyPath);
    }

    [Fact]
    public void RegisteredPaths_RejectDivergentClassBinary()
    {
        const string native = @"C:\Vendor\K3DAsyncEngine\DLL\x64\Release\K3DAsyncEngine.dll";
        var snapshot = ReadySnapshot(native) with
        {
            EventHandler = ReadySnapshot(native).EventHandler with
            {
                InprocServerPath =
                    @"C:\Other\K3DAsyncEngine\DLL\x64\Release\K3DAsyncEngine.dll"
            }
        };

        Assert.Throws<InstalledK3dInteropMetadataUnavailableException>(
            () => InstalledK3dInteropMetadata.ResolveRegisteredPaths(snapshot));
    }

    [Fact]
    public void RegisteredPaths_RejectUnexpectedNativeLayout()
    {
        const string native = @"C:\Vendor\K3DAsyncEngine\bin\K3DAsyncEngine.dll";

        Assert.Throws<InstalledK3dInteropMetadataUnavailableException>(
            () => InstalledK3dInteropMetadata.ResolveRegisteredPaths(ReadySnapshot(native)));
    }

    private static K3dRegistrySnapshot ReadySnapshot(string nativePath) => new(
        true,
        nativePath,
        new K3dClassRegistrationSnapshot(
            true,
            K3dComConstants.KaEngineProgId,
            K3dComConstants.KaEngineClassId,
            nativePath,
            K3dComConstants.ApartmentThreadingModel,
            K3dComConstants.TypeLibraryId),
        new K3dClassRegistrationSnapshot(
            true,
            K3dComConstants.KaEventHandlerProgId,
            K3dComConstants.KaEventHandlerClassId,
            nativePath,
            K3dComConstants.ApartmentThreadingModel,
            K3dComConstants.TypeLibraryId),
        false);

    private static FileStream OpenForWrite(string path) => new(
        path,
        FileMode.Open,
        FileAccess.Write,
        FileShare.ReadWrite | FileShare.Delete);
}
