using System.Security.AccessControl;
using System.Security.Principal;
using System.Runtime.Versioning;
using MBN_STOCK_WEBVIEW.Infrastructure;

namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests;

public sealed class TrustedManualDirectoryTests
{
    [Fact]
    public void Prepare_creates_nested_private_directory_with_protected_acl()
    {
        using var parent = new TemporaryDirectory();
        var target = Path.Combine(parent.Path, "operator", "private");

        var prepared = TrustedManualDirectory.PreparePrivateWritableDirectory(target);

        Assert.Equal(Path.GetFullPath(target), prepared);
        Assert.True(Directory.Exists(prepared));
        if (OperatingSystem.IsWindows())
        {
            AssertPrivateAcl(prepared);
        }
    }

    [SupportedOSPlatform("windows")]
    private static void AssertPrivateAcl(string directory)
    {
        var security = new DirectoryInfo(directory).GetAccessControl(
            AccessControlSections.Access);
        Assert.True(security.AreAccessRulesProtected);
        Assert.DoesNotContain(
            security.GetAccessRules(true, true, typeof(SecurityIdentifier))
                .Cast<FileSystemAccessRule>(),
            rule => rule.IsInherited);
    }

    [Fact]
    public void Writable_store_rejects_acl_expanded_to_world_readable()
    {
        if (!OperatingSystem.IsWindows())
        {
            return;
        }

        using var directory = new TemporaryDirectory();
        TrustedManualDirectory.PreparePrivateWritableDirectory(directory.Path);
        var info = new DirectoryInfo(directory.Path);
        var security = info.GetAccessControl();
        security.AddAccessRule(new FileSystemAccessRule(
            new SecurityIdentifier(WellKnownSidType.WorldSid, null),
            FileSystemRights.Read,
            AccessControlType.Allow));
        info.SetAccessControl(security);

        Assert.Throws<S5025TrustedManualDataException>(() =>
            new S5025TrustedManualFileStore(directory.Path));
    }

    [Fact]
    public void Writable_lease_pins_directory_against_rename_race()
    {
        if (!OperatingSystem.IsWindows())
        {
            return;
        }

        using var parent = new TemporaryDirectory();
        var directory = Path.Combine(parent.Path, "operator");
        TrustedManualDirectory.PreparePrivateWritableDirectory(directory);
        using (var store = new S5025TrustedManualFileStore(directory))
        {
            Assert.ThrowsAny<IOException>(() =>
                Directory.Move(directory, Path.Combine(parent.Path, "redirected")));
        }

        Directory.Move(directory, Path.Combine(parent.Path, "released"));
    }

    [Fact]
    public void Prepare_rejects_reparse_ancestor_before_creating_target()
    {
        using var parent = new TemporaryDirectory();
        using var outside = new TemporaryDirectory();
        var link = Path.Combine(parent.Path, "linked");
        try
        {
            Directory.CreateSymbolicLink(link, outside.Path);
        }
        catch (Exception exception) when (exception is
            UnauthorizedAccessException or IOException or PlatformNotSupportedException)
        {
            return;
        }

        Assert.Throws<S5025TrustedManualDataException>(() =>
            TrustedManualDirectory.PreparePrivateWritableDirectory(
                Path.Combine(link, "must-not-be-created")));
        Assert.False(Directory.Exists(Path.Combine(outside.Path, "must-not-be-created")));
    }

    private sealed class TemporaryDirectory : IDisposable
    {
        public TemporaryDirectory()
        {
            Path = System.IO.Path.Combine(
                System.IO.Path.GetTempPath(),
                Guid.NewGuid().ToString("N"));
            Directory.CreateDirectory(Path);
        }

        public string Path { get; }

        public void Dispose()
        {
            try
            {
                Directory.Delete(Path, recursive: true);
            }
            catch (Exception exception) when (exception is
                IOException or UnauthorizedAccessException)
            {
                // Cleanup must not hide the test result.
            }
        }
    }
}
