using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.GZip;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Readers;
using SharpCompress.Test.Mocks;
using SharpCompress.Writers;
using Xunit;

namespace SharpCompress.Test.GZip;

public class AsyncTests : TestBase
{
    [Fact]
    public async ValueTask Reader_Async_Extract_All()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
#if NETFRAMEWORK
        using var stream = File.OpenRead(testArchive);
#else
        await using var stream = File.OpenRead(testArchive);
#endif
        await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));

        await reader.WriteAllToDirectoryAsync(SCRATCH_FILES_PATH);

        // Just verify some files were extracted
        var extractedFiles = Directory.GetFiles(
            SCRATCH_FILES_PATH,
            "*",
            SearchOption.AllDirectories
        );
        Assert.True(extractedFiles.Length > 0, "No files were extracted");
    }

    [Fact]
    public async ValueTask Reader_Async_Extract_Single_Entry()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
#if NETFRAMEWORK
        using var stream = File.OpenRead(testArchive);
#else
        await using var stream = File.OpenRead(testArchive);
#endif
        await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));

        while (await reader.MoveToNextEntryAsync())
        {
            if (!reader.Entry.IsDirectory)
            {
                var outputPath = Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key!);
                Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
#if NETFRAMEWORK
                using var outputStream = File.Create(outputPath);
#else
                await using var outputStream = File.Create(outputPath);
#endif
                await reader.WriteEntryToAsync(outputStream);
                break; // Just test one entry
            }
        }
    }

    [Fact]
    public async ValueTask Archive_Entry_Async_Open_Stream()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
        await using var archive = await GZipArchive.OpenAsyncArchive(
            new AsyncOnlyStream(File.OpenRead(testArchive))
        );

        await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory).Take(1))
        {
#if NETFRAMEWORK
            using var entryStream = await entry.OpenEntryStreamAsync();
#else
            await using var entryStream = await entry.OpenEntryStreamAsync();
#endif
            Assert.NotNull(entryStream);
            Assert.True(entryStream.CanRead);

            // Read some data to verify it works
            var buffer = new byte[1024];
            var read = await entryStream.ReadAsync(buffer, 0, buffer.Length);
            Assert.True(read > 0);
        }
    }

    [Fact]
    public async ValueTask Writer_Async_Write_Single_File()
    {
        var outputPath = Path.Combine(SCRATCH_FILES_PATH, "async_test.zip");

#if NETFRAMEWORK
        using (var stream = File.Create(outputPath))
#else
        await using (var stream = File.Create(outputPath))
#endif
        await using (
            var writer = await WriterFactory.OpenAsyncWriter(
                new AsyncOnlyStream(stream),
                ArchiveType.Zip,
                new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = false }
            )
        )
        {
            var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");

#if NETFRAMEWORK
            using var fileStream = File.OpenRead(testFile);
#else
            await using var fileStream = File.OpenRead(testFile);
#endif
            await writer.WriteAsync("test_entry.bin", fileStream, new DateTime(2023, 1, 1));
        }

        // Verify the archive was created and contains the entry
        Assert.True(File.Exists(outputPath));
        await using var archive = await ZipArchive.OpenAsyncArchive(outputPath);
        Assert.Single(await archive.EntriesAsync.Where(e => !e.IsDirectory).ToListAsync());
    }

    [Fact]
    public async ValueTask Async_With_Cancellation_Token()
    {
        using var cts = new CancellationTokenSource();
        cts.CancelAfter(10000); // 10 seconds should be plenty

        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
#if NETFRAMEWORK
        using var stream = File.OpenRead(testArchive);
#else
        await using var stream = File.OpenRead(testArchive);
#endif
        await using var reader = await ReaderFactory.OpenAsyncReader(
            new AsyncOnlyStream(stream),
            cancellationToken: cts.Token
        );

        await reader.WriteAllToDirectoryAsync(SCRATCH_FILES_PATH, cancellationToken: cts.Token);

        // Just verify some files were extracted
        var extractedFiles = Directory.GetFiles(
            SCRATCH_FILES_PATH,
            "*",
            SearchOption.AllDirectories
        );
        Assert.True(extractedFiles.Length > 0, "No files were extracted");
    }

    [Fact]
    public async ValueTask Stream_Extensions_Async()
    {
        var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
        var outputPath = Path.Combine(SCRATCH_FILES_PATH, "async_copy.bin");
#if NETFRAMEWORK
        using var inputStream = File.OpenRead(testFile);
        using var outputStream = File.Create(outputPath);
#else
        await using var inputStream = File.OpenRead(testFile);
        await using var outputStream = File.Create(outputPath);
#endif

        // Test the async extension method
        var buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            await outputStream.WriteAsync(buffer, 0, bytesRead);
        }

        Assert.True(File.Exists(outputPath));
        Assert.True(new FileInfo(outputPath).Length > 0);
    }

    [Fact]
    public async ValueTask EntryStream_ReadAsync_Works()
    {
        var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
#if NETFRAMEWORK
        using var stream = File.OpenRead(testArchive);
#else
        await using var stream = File.OpenRead(testArchive);
#endif
        await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream));

        while (await reader.MoveToNextEntryAsync())
        {
            if (!reader.Entry.IsDirectory)
            {
#if NETFRAMEWORK
                using var entryStream = await reader.OpenEntryStreamAsync();
#else
                await using var entryStream = await reader.OpenEntryStreamAsync();
#endif
                var buffer = new byte[4096];
                var totalRead = 0;
                int bytesRead;

                // Test ReadAsync on EntryStream
                while ((bytesRead = await entryStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    totalRead += bytesRead;
                }

                Assert.True(totalRead > 0, "Should have read some data from entry stream");
                break; // Test just one entry
            }
        }
    }

    [Fact]
    public async ValueTask CompressionStream_Async_ReadWrite()
    {
        var testData = new byte[1024];
        new Random(42).NextBytes(testData);

        var compressedPath = Path.Combine(SCRATCH_FILES_PATH, "async_compressed.gz");

        // Test async write with GZipStream
#if NETFRAMEWORK
        using (var fileStream = File.Create(compressedPath))
        using (var gzipStream = new GZipStream(fileStream, CompressionMode.Compress))
#else
        await using (var fileStream = File.Create(compressedPath))
        await using (var gzipStream = new GZipStream(fileStream, CompressionMode.Compress))
#endif
        {
            await gzipStream.WriteAsync(testData, 0, testData.Length);
            await gzipStream.FlushAsync();
        }

        Assert.True(File.Exists(compressedPath));
        Assert.True(new FileInfo(compressedPath).Length > 0);
#if NETFRAMEWORK
        using (var fileStream = File.OpenRead(compressedPath))
        using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress))
#else
        // Test async read with GZipStream
        await using (var fileStream = File.OpenRead(compressedPath))
        await using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress))
#endif
        {
            var decompressed = new byte[testData.Length];
            var totalRead = 0;
            int bytesRead;
            while (
                totalRead < decompressed.Length
                && (
                    bytesRead = await gzipStream.ReadAsync(
                        decompressed,
                        totalRead,
                        decompressed.Length - totalRead
                    )
                ) > 0
            )
            {
                totalRead += bytesRead;
            }

            Assert.Equal(testData.Length, totalRead);
            Assert.Equal(testData, decompressed);
        }
    }
}
