using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.Rar;
using SharpCompress.Common;
using SharpCompress.Compressors.LZMA.Utilities;
using SharpCompress.Readers;
using SharpCompress.Test.Mocks;
using Xunit;

namespace SharpCompress.Test.Rar;

public class RarArchiveAsyncTests : ArchiveTests
{
    [Theory]
    [InlineData("Rar15.rar")]
    [InlineData("Rar2.rar")]
    [InlineData("Rar.rar")]
    [InlineData("Rar.Audio_program.rar")]
    [InlineData("Rar5.rar")]
    [InlineData("Rar5.solid.rar")]
    public async ValueTask Rar_Archive_Recently_Changed_Unpackers_Async(string filename)
    {
        var extractedEntries = 0;
        await using var archive = await RarArchive.OpenAsyncArchive(
            Path.Combine(TEST_ARCHIVES_PATH, filename),
            new ReaderOptions { LookForHeader = true }
        );

        await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
        {
            using var output = new AsyncOnlyStream(new MemoryStream());
            await entry.WriteToAsync(output);
            extractedEntries++;
        }

        Assert.True(extractedEntries > 0);
    }

    [Fact]
    public async ValueTask Rar_EncryptedFileAndHeader_Archive_Async() =>
        await ReadRarPasswordAsync("Rar.encrypted_filesAndHeader.rar", "test");

    [Fact]
    public async ValueTask Rar_EncryptedFileAndHeader_NoPasswordExceptionTest_Async() =>
        await Assert.ThrowsAsync(
            typeof(CryptographicException),
            async () => await ReadRarPasswordAsync("Rar.encrypted_filesAndHeader.rar", null)
        );

    [Fact]
    public async ValueTask Rar5_EncryptedFileAndHeader_Archive_Async() =>
        await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", "test");

    [Fact]
    public async ValueTask Rar5_EncryptedFileAndHeader_Archive_Err_Async() =>
        await Assert.ThrowsAsync(
            typeof(CryptographicException),
            async () => await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", "failed")
        );

    [Fact]
    public async ValueTask Rar5_EncryptedFileAndHeader_NoPasswordExceptionTest_Async() =>
        await Assert.ThrowsAsync(
            typeof(CryptographicException),
            async () => await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", null)
        );

    [Fact]
    public async ValueTask Rar_EncryptedFileOnly_Archive_Async() =>
        await ReadRarPasswordAsync("Rar.encrypted_filesOnly.rar", "test");

    [Fact]
    public async ValueTask Rar_EncryptedFileOnly_Archive_Err_Async() =>
        await Assert.ThrowsAsync(
            typeof(CryptographicException),
            async () => await ReadRarPasswordAsync("Rar5.encrypted_filesOnly.rar", "failed")
        );

    [Fact]
    public async ValueTask Rar5_EncryptedFileOnly_Archive_Async() =>
        await ReadRarPasswordAsync("Rar5.encrypted_filesOnly.rar", "test");

    [Fact]
    public async ValueTask Rar_Encrypted_Archive_Async() =>
        await ReadRarPasswordAsync("Rar.Encrypted.rar", "test");

    [Fact]
    public async ValueTask Rar5_Encrypted_Archive_Async() =>
        await ReadRarPasswordAsync("Rar5.encrypted_filesAndHeader.rar", "test");

    private async ValueTask ReadRarPasswordAsync(string testArchive, string? password)
    {
        using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive)))
        await using (
            var archive = await RarArchive.OpenAsyncArchive(
                stream,
                ReaderOptions.ForExternalStream with
                {
                    Password = password,
                    LeaveStreamOpen = true,
                }
            )
        )
        {
            await foreach (var entry in archive.EntriesAsync)
            {
                if (!entry.IsDirectory)
                {
                    Assert.Equal(CompressionType.Rar, entry.CompressionType);
                    await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
                }
            }
        }
        VerifyFiles();
    }

    [Fact]
    public async ValueTask Rar_Multi_Archive_Encrypted_Async() =>
        await Assert.ThrowsAsync<InvalidFormatException>(async () =>
            await ArchiveFileReadPasswordAsync("Rar.EncryptedParts.part01.rar", "test")
        );

    protected async Task ArchiveFileReadPasswordAsync(string archiveName, string password)
    {
        using (
            var archive = RarArchive.OpenArchive(
                Path.Combine(TEST_ARCHIVES_PATH, archiveName),
                ReaderOptions.ForFilePath with
                {
                    Password = password,
                    LeaveStreamOpen = true,
                }
            )
        )
        {
            foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
            {
                await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
            }
        }
        VerifyFiles();
    }

    [Fact]
    public async ValueTask Rar_None_ArchiveStreamRead_Async() =>
        await ArchiveStreamReadAsync("Rar.none.rar");

    [Fact]
    public async ValueTask Rar5_None_ArchiveStreamRead_Async() =>
        await ArchiveStreamReadAsync("Rar5.none.rar");

    [Fact]
    public async ValueTask Rar_ArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Rar.rar");

    [Fact]
    public async ValueTask Rar5_ArchiveStreamRead_Async() =>
        await ArchiveStreamReadAsync("Rar5.rar");

    [Fact]
    public async ValueTask Rar_test_invalid_exttime_ArchiveStreamRead_Async() =>
        await DoRar_test_invalid_exttime_ArchiveStreamReadAsync("Rar.test_invalid_exttime.rar");

    private async ValueTask DoRar_test_invalid_exttime_ArchiveStreamReadAsync(string filename)
    {
        using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
        using var archive = ArchiveFactory.OpenArchive(stream);
        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
        }
    }

    [Fact]
    public async ValueTask Rar_Jpg_ArchiveStreamRead_Async()
    {
        using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"));
        using (
            var archive = RarArchive.OpenArchive(
                stream,
                ReaderOptions.ForExternalStream with
                {
                    LookForHeader = true,
                }
            )
        )
        {
            foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
            {
                await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
            }
        }
        VerifyFiles();
    }

    [Fact]
    public async ValueTask Rar_IsSolidArchiveCheck_Async() =>
        await DoRar_IsSolidArchiveCheckAsync("Rar.rar");

    [Fact]
    public async ValueTask Rar5_IsSolidArchiveCheck_Async() =>
        await DoRar_IsSolidArchiveCheckAsync("Rar5.rar");

    private async ValueTask DoRar_IsSolidArchiveCheckAsync(string filename)
    {
        using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)))
        {
            using var archive = RarArchive.OpenArchive(stream);
            Assert.False(archive.IsSolid);
            foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
            {
                await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
            }
        }
        VerifyFiles();
    }

    [Fact]
    public async ValueTask Rar_IsSolidEntryStreamCheck_Async() =>
        await DoRar_IsSolidEntryStreamCheckAsync("Rar.solid.rar");

    private async ValueTask DoRar_IsSolidEntryStreamCheckAsync(string filename)
    {
        using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
        using var archive = RarArchive.OpenArchive(stream);
        Assert.True(archive.IsSolid);
        IArchiveEntry[] entries = archive.Entries.Where(a => !a.IsDirectory).ToArray();
        Assert.NotInRange(entries.Length, 0, 1);
        Assert.False(entries[0].IsSolid);
        var testEntry = entries[1];
        Assert.True(testEntry.IsSolid);

        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            using (var crcStream = new CrcCheckStream((uint)entry.Crc))
            {
                using var eStream = await entry.OpenEntryStreamAsync();
                await eStream.CopyToAsync(crcStream);
            }
            if (entry == testEntry)
            {
                break;
            }
        }
    }

    [Fact]
    public async ValueTask Rar_Solid_ArchiveStreamRead_Async() =>
        await ArchiveStreamReadAsync("Rar.solid.rar");

    [Fact]
    public async ValueTask Rar5_Solid_ArchiveStreamRead_Async() =>
        await ArchiveStreamReadAsync("Rar5.solid.rar");

    [Fact]
    public async ValueTask Rar_Solid_StreamRead_Extract_All_Async() =>
        await ArchiveStreamReadExtractAllAsync("Rar.solid.rar", CompressionType.Rar);

    [Fact]
    public async ValueTask Rar5_Solid_StreamRead_Extract_All_Async() =>
        await ArchiveStreamReadExtractAllAsync("Rar5.solid.rar", CompressionType.Rar);

    [Fact]
    public async ValueTask Rar_Multi_ArchiveStreamRead_Async() =>
        await DoRar_Multi_ArchiveStreamReadAsync(
            [
                "Rar.multi.part01.rar",
                "Rar.multi.part02.rar",
                "Rar.multi.part03.rar",
                "Rar.multi.part04.rar",
                "Rar.multi.part05.rar",
                "Rar.multi.part06.rar",
            ],
            false
        );

    [Fact]
    public async ValueTask Rar5_Multi_ArchiveStreamRead_Async() =>
        await DoRar_Multi_ArchiveStreamReadAsync(
            [
                "Rar5.multi.part01.rar",
                "Rar5.multi.part02.rar",
                "Rar5.multi.part03.rar",
                "Rar5.multi.part04.rar",
                "Rar5.multi.part05.rar",
                "Rar5.multi.part06.rar",
            ],
            false
        );

    private async ValueTask DoRar_Multi_ArchiveStreamReadAsync(string[] archives, bool isSolid)
    {
        using var archive = RarArchive.OpenArchive(
            archives
                .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s))
                .Select(File.OpenRead)
                .ToArray()
        );
        Assert.Equal(archive.IsSolid, isSolid);
        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
        }
    }

    [Fact]
    public async ValueTask Rar5_MultiSolid_ArchiveStreamRead_Async() =>
        await DoRar_Multi_ArchiveStreamReadAsync(
            [
                "Rar.multi.solid.part01.rar",
                "Rar.multi.solid.part02.rar",
                "Rar.multi.solid.part03.rar",
                "Rar.multi.solid.part04.rar",
                "Rar.multi.solid.part05.rar",
                "Rar.multi.solid.part06.rar",
            ],
            true
        );

    [Fact]
    public async ValueTask RarNoneArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar.none.rar");

    [Fact]
    public async ValueTask Rar5NoneArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar5.none.rar");

    [Fact]
    public async ValueTask Rar_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar.rar");

    [Fact]
    public async ValueTask Rar5_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar5.rar");

    [Fact]
    public async ValueTask Rar_ArchiveFileRead_HasDirectories_Async() =>
        await DoRar_ArchiveFileRead_HasDirectoriesAsync("Rar.rar");

    [Fact]
    public async ValueTask Rar5_ArchiveFileRead_HasDirectories_Async() =>
        await DoRar_ArchiveFileRead_HasDirectoriesAsync("Rar5.rar");

    private Task DoRar_ArchiveFileRead_HasDirectoriesAsync(string filename)
    {
        using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
        using var archive = RarArchive.OpenArchive(stream);
        Assert.False(archive.IsSolid);
        Assert.Contains(true, archive.Entries.Select(entry => entry.IsDirectory));
        return Task.CompletedTask;
    }

    [Fact]
    public async ValueTask Rar_Jpg_ArchiveFileRead_Async()
    {
        using (
            var archive = RarArchive.OpenArchive(
                Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"),
                ReaderOptions.ForFilePath with
                {
                    LookForHeader = true,
                }
            )
        )
        {
            foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
            {
                await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
            }
        }
        VerifyFiles();
    }

    [Fact]
    public async ValueTask Rar_Solid_ArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar.solid.rar");

    [Fact]
    public async ValueTask Rar5_Solid_ArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar5.solid.rar");

    [Fact]
    public async ValueTask Rar2_Multi_ArchiveStreamRead_Async() =>
        await DoRar_Multi_ArchiveStreamReadAsync(
            [
                "Rar2.multi.rar",
                "Rar2.multi.r00",
                "Rar2.multi.r01",
                "Rar2.multi.r02",
                "Rar2.multi.r03",
                "Rar2.multi.r04",
                "Rar2.multi.r05",
            ],
            false
        );

    [Fact]
    public async ValueTask Rar2_Multi_ArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar2.multi.rar");

    [Fact]
    public async ValueTask Rar2_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar2.rar");

    [Fact]
    public async ValueTask Rar15_ArchiveFileRead_Async()
    {
        UseExtensionInsteadOfNameToVerify = true;
        UseCaseInsensitiveToVerify = true;
        await ArchiveFileReadAsync("Rar15.rar");
    }

    [Fact]
    public void Rar15_ArchiveVersionTest_Async()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar15.rar");

        using var archive = RarArchive.OpenArchive(testArchive);
        Assert.Equal(1, archive.MinVersion);
        Assert.Equal(1, archive.MaxVersion);
    }

    [Fact]
    public void Rar2_ArchiveVersionTest_Async()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar2.rar");

        using var archive = RarArchive.OpenArchive(testArchive);
        Assert.Equal(2, archive.MinVersion);
        Assert.Equal(2, archive.MaxVersion);
    }

    [Fact]
    public void Rar4_ArchiveVersionTest_Async()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar4.multi.part01.rar");

        using var archive = RarArchive.OpenArchive(testArchive);
        Assert.Equal(3, archive.MinVersion);
        Assert.Equal(4, archive.MaxVersion);
    }

    [Fact]
    public void Rar5_ArchiveVersionTest_Async()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.rar");

        using var archive = RarArchive.OpenArchive(testArchive);
        Assert.Equal(5, archive.MinVersion);
        Assert.Equal(6, archive.MaxVersion);
    }

    [Fact]
    public async ValueTask Rar4_Multi_ArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar4.multi.part01.rar");

    [Fact]
    public async ValueTask Rar4_ArchiveFileRead_Async() => await ArchiveFileReadAsync("Rar4.rar");

    [Fact]
    public void Rar_GetPartsSplit_Async() =>
        ArchiveGetParts(
            new[]
            {
                "Rar4.split.001",
                "Rar4.split.002",
                "Rar4.split.003",
                "Rar4.split.004",
                "Rar4.split.005",
                "Rar4.split.006",
            }
        );

    [Fact]
    public void Rar_GetPartsOld_Async() =>
        ArchiveGetParts(
            new[]
            {
                "Rar2.multi.rar",
                "Rar2.multi.r00",
                "Rar2.multi.r01",
                "Rar2.multi.r02",
                "Rar2.multi.r03",
                "Rar2.multi.r04",
                "Rar2.multi.r05",
            }
        );

    [Fact]
    public void Rar_GetPartsNew_Async() =>
        ArchiveGetParts(
            new[]
            {
                "Rar4.multi.part01.rar",
                "Rar4.multi.part02.rar",
                "Rar4.multi.part03.rar",
                "Rar4.multi.part04.rar",
                "Rar4.multi.part05.rar",
                "Rar4.multi.part06.rar",
                "Rar4.multi.part07.rar",
            }
        );

    [Fact]
    public async ValueTask Rar4_Multi_ArchiveStreamRead_Async() =>
        await DoRar_Multi_ArchiveStreamReadAsync(
            [
                "Rar4.multi.part01.rar",
                "Rar4.multi.part02.rar",
                "Rar4.multi.part03.rar",
                "Rar4.multi.part04.rar",
                "Rar4.multi.part05.rar",
                "Rar4.multi.part06.rar",
                "Rar4.multi.part07.rar",
            ],
            false
        );

    [Fact]
    public async ValueTask Rar4_Split_ArchiveStreamRead_Async() =>
        await ArchiveStreamMultiReadAsync(
            null,
            [
                "Rar4.split.001",
                "Rar4.split.002",
                "Rar4.split.003",
                "Rar4.split.004",
                "Rar4.split.005",
                "Rar4.split.006",
            ]
        );

    [Fact]
    public async ValueTask Rar4_Multi_ArchiveFirstFileRead_Async() =>
        await ArchiveFileReadAsync("Rar4.multi.part01.rar");

    [Fact]
    public async ValueTask Rar4_Split_ArchiveFirstFileRead_Async() =>
        await ArchiveFileReadAsync("Rar4.split.001");

    [Fact]
    public async ValueTask Rar4_Split_ArchiveStreamFirstFileRead_Async() =>
        await ArchiveStreamMultiReadAsync(null, ["Rar4.split.001"]);

    [Fact]
    public async ValueTask Rar4_Split_ArchiveOpen_Async() =>
        await ArchiveOpenStreamReadAsync(
            null,
            "Rar4.split.001",
            "Rar4.split.002",
            "Rar4.split.003",
            "Rar4.split.004",
            "Rar4.split.005",
            "Rar4.split.006"
        );

    [Fact]
    public async ValueTask Rar4_Multi_ArchiveOpen_Async() =>
        await ArchiveOpenStreamReadAsync(
            null,
            "Rar4.multi.part01.rar",
            "Rar4.multi.part02.rar",
            "Rar4.multi.part03.rar",
            "Rar4.multi.part04.rar",
            "Rar4.multi.part05.rar",
            "Rar4.multi.part06.rar",
            "Rar4.multi.part07.rar"
        );

    [Fact]
    public void Rar4_Multi_ArchiveOpenEntryVolumeIndexTest_Async() =>
        ArchiveOpenEntryVolumeIndexTest(
            [
                [0, 1],
                [1, 5],
                [5, 6],
            ],
            null,
            "Rar4.multi.part01.rar",
            "Rar4.multi.part02.rar",
            "Rar4.multi.part03.rar",
            "Rar4.multi.part04.rar",
            "Rar4.multi.part05.rar",
            "Rar4.multi.part06.rar",
            "Rar4.multi.part07.rar"
        );

    [Fact]
    public async ValueTask Rar_Multi_ArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar.multi.part01.rar");

    [Fact]
    public async ValueTask Rar5_Multi_ArchiveFileRead_Async() =>
        await ArchiveFileReadAsync("Rar5.multi.part01.rar");

    [Fact]
    public void Rar_IsFirstVolume_True_Async() => DoRar_IsFirstVolume_True("Rar.multi.part01.rar");

    [Fact]
    public void Rar5_IsFirstVolume_True_Async() =>
        DoRar_IsFirstVolume_True("Rar5.multi.part01.rar");

    private void DoRar_IsFirstVolume_True(string firstFilename)
    {
        using var archive = RarArchive.OpenArchive(Path.Combine(TEST_ARCHIVES_PATH, firstFilename));
        Assert.True(archive.IsMultipartVolume());
        Assert.True(archive.IsFirstVolume());
    }

    [Fact]
    public void Rar_IsFirstVolume_False_Async() =>
        DoRar_IsFirstVolume_False("Rar.multi.part03.rar");

    [Fact]
    public void Rar5_IsFirstVolume_False_Async() =>
        DoRar_IsFirstVolume_False("Rar5.multi.part03.rar");

    private void DoRar_IsFirstVolume_False(string notFirstFilename)
    {
        using var archive = RarArchive.OpenArchive(
            Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename)
        );
        Assert.True(archive.IsMultipartVolume());
        Assert.False(archive.IsFirstVolume());
    }

    [Fact]
    public async ValueTask Rar5_CRC_Blake2_Archive_Async() =>
        await ArchiveFileReadAsync("Rar5.crc_blake2.rar");

    [Fact]
    void Rar_Iterate_Archive_Async() =>
        ArchiveFileSkip("Rar.rar", "Failure jpg exe Empty jpg\\test.jpg exe\\test.exe тест.txt");

    [Fact]
    public void Rar2_Iterate_Archive_Async() =>
        ArchiveFileSkip("Rar2.rar", "Failure Empty тест.txt jpg\\test.jpg exe\\test.exe jpg exe");

    [Fact]
    public void Rar4_Iterate_Archive_Async() =>
        ArchiveFileSkip("Rar4.rar", "Failure Empty jpg exe тест.txt jpg\\test.jpg exe\\test.exe");

    [Fact]
    public void Rar5_Iterate_Archive_Async() =>
        ArchiveFileSkip("Rar5.rar", "Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe");

    [Fact]
    public void Rar_Encrypted_Iterate_Archive_Async() =>
        ArchiveFileSkip(
            "Rar.encrypted_filesOnly.rar",
            "Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe"
        );

    [Fact]
    public void Rar5_Encrypted_Iterate_Archive_Async() =>
        ArchiveFileSkip(
            "Rar5.encrypted_filesOnly.rar",
            "Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe"
        );

    private async ValueTask ArchiveStreamReadAsync(string testArchive)
    {
        testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
        using var stream = File.OpenRead(testArchive);
        using var archive = ArchiveFactory.OpenArchive(stream);
        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
        }
        VerifyFiles();
    }

    private async ValueTask ArchiveStreamReadExtractAllAsync(
        string testArchive,
        CompressionType compression
    )
    {
        testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
        using var stream = File.OpenRead(testArchive);
        await using var archive = await ArchiveFactory.OpenAsyncArchive(
            new AsyncOnlyStream(stream)
        );
        Assert.True(await archive.IsSolidAsync());
        await using (var reader = await archive.ExtractAllEntriesAsync())
        {
            while (await reader.MoveToNextEntryAsync())
            {
                if (!reader.Entry.IsDirectory)
                {
                    Assert.Equal(compression, reader.Entry.CompressionType);
                    await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH);
                }
            }
        }
        VerifyFiles();

        await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
        {
            await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
        }
        VerifyFiles();
    }

    private async ValueTask ArchiveFileReadAsync(string testArchive)
    {
        testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
        using var archive = ArchiveFactory.OpenArchive(testArchive);
        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
        }
        VerifyFiles();
    }

    private async ValueTask ArchiveStreamMultiReadAsync(
        ReaderOptions? readerOptions,
        params string[] testArchives
    )
    {
        var paths = testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x));
        using var archive = ArchiveFactory.OpenArchive(
            paths.Select(a => new FileInfo(a)).ToArray(),
            readerOptions
        );
        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
        }
        VerifyFiles();
    }

    /// <summary>
    /// Tests for Issue #1050 - RAR extraction with WriteToDirectoryAsync creates folders
    /// but places all files at the top level instead of in their subdirectories.
    /// </summary>
    [Fact]
    public async ValueTask Rar_Issue1050_WriteToDirectoryAsync_ExtractsToSubdirectories()
    {
        var testFile = "Rar.issue1050.rar";
        using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile));
        await using var archive = await RarArchive.OpenAsyncArchive(fileStream);

        // Extract using archive.WriteToDirectoryAsync without explicit options
        await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH);

        // Verify files are in their subdirectories, not at the root
        Assert.True(
            File.Exists(Path.Combine(SCRATCH_FILES_PATH, "PhysicsBraid", "263825.tr11dtp")),
            "File should be in PhysicsBraid subdirectory"
        );
        Assert.True(
            File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Animations", "15441.tr11anim")),
            "File should be in Animations subdirectory"
        );
        Assert.True(
            File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766728.tr11dtp")),
            "File should be in Braid subdirectory"
        );
        Assert.True(
            File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766832.tr11dtp")),
            "File should be in Braid subdirectory"
        );
        Assert.True(
            File.Exists(Path.Combine(SCRATCH_FILES_PATH, "HeadBraid", "321353.tr11modeldata")),
            "File should be in HeadBraid subdirectory"
        );

        // NOTE: The file size check is omitted because there's a separate pre-existing bug
        // in the async RAR stream implementation that causes incorrect file sizes.
        // This test only verifies the directory structure fix.
    }

    private async ValueTask ArchiveOpenStreamReadAsync(
        ReaderOptions? readerOptions,
        params string[] testArchives
    )
    {
        var paths = testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x));
        using var archive = ArchiveFactory.OpenArchive(
            paths.Select(f => new FileInfo(f)).ToArray(),
            readerOptions
        );
        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH);
        }
        VerifyFiles();
    }
}
