using System.IO;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Compressors.Xz;
using Xunit;

namespace SharpCompress.Test.Xz;

public class XzBlockAsyncTests : XzTestsBase
{
    protected override void Rewind(Stream stream) => stream.Position = 12;

    protected override void RewindIndexed(Stream stream) => stream.Position = 12;

    private static async Task<byte[]> ReadBytesAsync(XZBlock block, int bytesToRead)
    {
        var buffer = new byte[bytesToRead];
        var read = await block.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false);
        if (read != bytesToRead)
        {
            throw new EndOfStreamException();
        }

        return buffer;
    }

    [Fact]
    public async ValueTask OnFindIndexBlockThrowAsync()
    {
        var bytes = new byte[] { 0 };
        using Stream indexBlockStream = new MemoryStream(bytes);
        using var xzBlock = new XZBlock(indexBlockStream, CheckType.CRC64, 8);
        await Assert.ThrowsAsync<XZIndexMarkerReachedException>(async () =>
        {
            await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false);
        });
    }

    [Fact]
    public async ValueTask CrcIncorrectThrowsAsync()
    {
        var bytes = (byte[])Compressed.Clone();
        bytes[20]++;
        using Stream badCrcStream = new MemoryStream(bytes);
        Rewind(badCrcStream);
        using var xzBlock = new XZBlock(badCrcStream, CheckType.CRC64, 8);
        var ex = await Assert.ThrowsAsync<InvalidFormatException>(async () =>
        {
            await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false);
        });
        Assert.Equal("Block header corrupt", ex.Message);
    }

    [Fact]
    public async ValueTask CanReadMAsync()
    {
        using var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
        Assert.Equal(
            Encoding.ASCII.GetBytes("M"),
            await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false)
        );
    }

    [Fact]
    public async ValueTask CanReadMaryAsync()
    {
        using var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
        Assert.Equal(
            Encoding.ASCII.GetBytes("M"),
            await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false)
        );
        Assert.Equal(
            Encoding.ASCII.GetBytes("a"),
            await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false)
        );
        Assert.Equal(
            Encoding.ASCII.GetBytes("ry"),
            await ReadBytesAsync(xzBlock, 2).ConfigureAwait(false)
        );
    }

    [Fact]
    public async ValueTask CanReadPoemWithStreamReaderAsync()
    {
        using var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
        using var sr = new StreamReader(xzBlock);
        Assert.Equal(Original, await sr.ReadToEndAsync().ConfigureAwait(false));
    }

    [Fact]
    public async ValueTask NoopWhenNoPaddingAsync()
    {
        // CompressedStream's only block has no padding.
        using var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
        using var sr = new StreamReader(xzBlock);
        await sr.ReadToEndAsync().ConfigureAwait(false);
        Assert.Equal(0L, CompressedStream.Position % 4L);
    }

    [Fact]
    public async ValueTask SkipsPaddingWhenPresentAsync()
    {
        // CompressedIndexedStream uses CRC32 checks.
        using var xzBlock = new XZBlock(CompressedIndexedStream, CheckType.CRC32, 4);
        using var sr = new StreamReader(xzBlock);
        await sr.ReadToEndAsync().ConfigureAwait(false);
        Assert.Equal(0L, CompressedIndexedStream.Position % 4L);
    }

    [Fact]
    public async ValueTask HandlesPaddingInUnalignedBlockAsync()
    {
        var compressedUnaligned = new byte[Compressed.Length + 1];
        Compressed.CopyTo(compressedUnaligned, 1);
        var compressedUnalignedStream = new MemoryStream(compressedUnaligned);
        compressedUnalignedStream.Position = 13;

        // Compressed's only block has no padding.
        using var xzBlock = new XZBlock(compressedUnalignedStream, CheckType.CRC64, 8);
        using var sr = new StreamReader(xzBlock);
        await sr.ReadToEndAsync().ConfigureAwait(false);
        Assert.Equal(1L, compressedUnalignedStream.Position % 4L);
    }
}
