using System.Runtime.InteropServices;

namespace MBN_STOCK_WEBVIEW.Playout.Tests;

public sealed class TrustedPlayoutAssetPathTests
{
    private static readonly IReadOnlySet<string> Allowed = new HashSet<string>(
        [".vrv", ".png"],
        StringComparer.OrdinalIgnoreCase);

    [Fact]
    public void ResolveRelativeFile_AcceptsOneInternalRegularFile()
    {
        using var root = TemporarySceneDirectory.Create("studio.vrv");

        var resolved = TrustedPlayoutAssetPath.ResolveRelativeFile(
            root.Path,
            "studio.vrv",
            Allowed);

        Assert.Equal(Path.Combine(root.Path, "studio.vrv"), resolved, ignoreCase: true);
    }

    [Theory]
    [InlineData("..\\outside.vrv")]
    [InlineData("C:\\outside.vrv")]
    public void ResolveRelativeFile_RejectsRootEscape(string input)
    {
        using var root = TemporarySceneDirectory.Create("studio.vrv");

        Assert.Throws<TrustedPlayoutAssetException>(() =>
            TrustedPlayoutAssetPath.ResolveRelativeFile(root.Path, input, Allowed));
    }

    [Fact]
    public void ResolveRelativeFile_RejectsMissingFile()
    {
        using var root = TemporarySceneDirectory.Create();

        Assert.Throws<TrustedPlayoutAssetException>(() =>
            TrustedPlayoutAssetPath.ResolveRelativeFile(root.Path, "missing.vrv", Allowed));
    }

    [Fact]
    public void ResolveAbsoluteFile_RejectsPickerSelectionOutsideTheRoot()
    {
        using var root = TemporarySceneDirectory.Create();
        using var outside = TemporarySceneDirectory.Create("outside.vrv");

        Assert.Throws<TrustedPlayoutAssetException>(() =>
            TrustedPlayoutAssetPath.ResolveAbsoluteFile(
                root.Path,
                Path.Combine(outside.Path, "outside.vrv"),
                Allowed));
    }

    [Fact]
    public void ResolveRelativeFile_RejectsHardLinkedFile()
    {
        using var root = TemporarySceneDirectory.Create();
        var parent = Directory.GetParent(root.Path)!.FullName;
        var outside = Path.Combine(parent, "outside.vrv");
        File.WriteAllText(outside, "outside trusted-looking bytes");
        var link = Path.Combine(root.Path, "linked.vrv");
        Assert.True(CreateHardLinkW(link, outside, IntPtr.Zero));

        Assert.Throws<TrustedPlayoutAssetException>(() =>
            TrustedPlayoutAssetPath.ResolveRelativeFile(root.Path, "linked.vrv", Allowed));
    }

    [Fact]
    public void ResolveRelativeFile_RejectsReparseDirectory()
    {
        using var root = TemporarySceneDirectory.Create();
        var parent = Directory.GetParent(root.Path)!.FullName;
        var outside = Path.Combine(parent, "outside");
        Directory.CreateDirectory(outside);
        File.WriteAllText(Path.Combine(outside, "studio.vrv"), "outside bytes");
        var link = Path.Combine(root.Path, "linked");
        try
        {
            Directory.CreateSymbolicLink(link, outside);
        }
        catch (Exception exception) when (
            exception is UnauthorizedAccessException or IOException or PlatformNotSupportedException)
        {
            throw Xunit.Sdk.SkipException.ForSkip(
                "The Windows test host does not permit creating a symbolic link.");
        }

        Assert.Throws<TrustedPlayoutAssetException>(() =>
            TrustedPlayoutAssetPath.ResolveRelativeFile(
                root.Path,
                "linked\\studio.vrv",
                Allowed));
    }

    [Fact]
    public void EnsureOpenedRegularFile_BindsTheHandleToTheExpectedPath()
    {
        using var root = TemporarySceneDirectory.Create("expected.vrv", "other.vrv");
        var expected = Path.Combine(root.Path, "expected.vrv");
        var other = Path.Combine(root.Path, "other.vrv");
        using var stream = new FileStream(
            other,
            FileMode.Open,
            FileAccess.Read,
            FileShare.Read);

        Assert.Throws<TrustedPlayoutAssetException>(() =>
            TrustedPlayoutAssetPath.EnsureOpenedRegularFile(
                stream.SafeFileHandle,
                expected));
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CreateHardLinkW(
        string fileName,
        string existingFileName,
        IntPtr securityAttributes);
}
