using System.IO;
using System.IO.Pipelines;
using CompatApiClient.Utils;
using ResultNet;
using SharpCompress.Archives.SevenZip;

namespace CompatBot.EventHandlers.LogParsing.ArchiveHandlers;

internal sealed class SevenZipHandler: IArchiveHandler
{
    private static readonly byte[] Header = [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C]; //7z....

    public long LogSize { get; private set; }
    public long SourcePosition { get; private set; }

    public Result CanHandle(string fileName, int fileSize, ReadOnlySpan<byte> header)
    {
        if (header.Length >= Header.Length && header[..Header.Length].SequenceEqual(Header)
            || header.Length == 0 && fileName.EndsWith(".7z", StringComparison.InvariantCultureIgnoreCase))
        {
            if (fileSize > Config.AttachmentSizeLimit)
                return Result.Failure().WithMessage($"Log size is too large for 7z format: {fileSize.AsStorageUnit()} (max allowed is {Config.AttachmentSizeLimit.AsStorageUnit()})");
            return Result.Success();
        }
        return Result.Failure();
    }

    public async Task FillPipeAsync(Stream sourceStream, PipeWriter writer, CancellationToken cancellationToken)
    {
        try
        {
            await using var fileStream = new FileStream(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 16384, FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose);
            await sourceStream.CopyToAsync(fileStream, 16384, cancellationToken).ConfigureAwait(false);
            fileStream.Seek(0, SeekOrigin.Begin);
            using var zipArchive = SevenZipArchive.Open(fileStream);
            using var zipReader = zipArchive.ExtractAllEntries();
            while (await zipReader.MoveToNextEntryAsync(cancellationToken).ConfigureAwait(false))
                if (!zipReader.Entry.IsDirectory
                    && zipReader.Entry.Key!.EndsWith(".log", StringComparison.InvariantCultureIgnoreCase)
                    && !zipReader.Entry.Key.Contains("tty.log", StringComparison.InvariantCultureIgnoreCase))
                {
                    LogSize = zipReader.Entry.Size;
                    await using var entryStream = await zipReader.OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
                    int read;
                    FlushResult flushed;
                    do
                    {
                        var memory = writer.GetMemory(Config.MinimumBufferSize);
                        read = await entryStream.ReadAsync(memory, cancellationToken);
                        if (read > 0)
                            writer.Advance(read);
                        flushed = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
                    } while (read > 0 && !(flushed.IsCompleted || flushed.IsCanceled || cancellationToken.IsCancellationRequested));
                    await writer.CompleteAsync();
                    return;
                }
            Config.Log.Warn("No 7z entries that match the log criteria");
        }
        catch (Exception e)
        {
            Config.Log.Error(e, "Error filling the log pipe");
        }
        await writer.CompleteAsync();
    }
}