using System.Diagnostics.CodeAnalysis;
using ImageConvertService.Biz;
using ImageConvertService.Biz.Models;
using ImageConvertService.ExceptionHandling;
using Microsoft.AspNetCore.Mvc;

namespace ImageConvertService.Controllers;

[Route("api/[controller]/[action]")]
[ApiController]
public class ConvertToWebpController(ImageConverter converter, ArchiveFileAccessor archiver, PdfImageReader pdf, ArchiveEntrySkipper filter) : Controller
{
    [HttpPost]
    public IActionResult SingleFile(IFormFile file)
    {
        var ms = new MemoryStream();
        file.CopyTo(ms);
        var bytes = ms.ToArray();
        var webpBytes = converter.ConvertToWebp(bytes);
        return File(webpBytes, "image/webp", $"{file.FileName}.webp");
    }

    [HttpPost]
    public async Task<FileContentResult> AnyArchiveQuery(
        IFormFile file,
        [FromQuery] string? skips,
        [FromQuery] OutputArchiveFormat? format = OutputArchiveFormat.Fast7Z)
    {
        return await AnyArchiveInternal(file.OpenReadStream(), Request.ContentLength, file.FileName, format, skips);
    }

    [HttpPost]
    public async Task<FileContentResult> AnyArchiveForm(
        IFormFile file,
        [FromForm] string? skips,
        [FromForm] OutputArchiveFormat? format = OutputArchiveFormat.Fast7Z)
    {
        return await AnyArchiveInternal(file.OpenReadStream(), Request.ContentLength, file.FileName, format, skips);
    }

    [HttpPost]
    public async Task<FileContentResult> AnyArchiveStreaming(
        [FromQuery] string filename,
        [FromQuery] string? skips,
        [FromQuery] OutputArchiveFormat format = OutputArchiveFormat.Fast7Z)
    {
        return await AnyArchiveInternal(Request.Body, Request.ContentLength, filename, format, skips);
    }

    private async Task<FileContentResult> AnyArchiveInternal(Stream inputStream, long? contentLength, string fileName, OutputArchiveFormat? format, string? skips)
    {
        format ??= OutputArchiveFormat.Fast7Z;

        if (inputStream.CanSeek)
        {
            inputStream.Position = 0;
            return ProcessArchiveInternal(inputStream, fileName, skips, format.Value);
        }

        try
        {
            contentLength = inputStream.Length;
        }
        catch (NotSupportedException)
        {
        }

        if (contentLength.HasValue && contentLength.Value < 2_000_000_000)
        {
            var ms = new MemoryStream();
            await inputStream.CopyToAsync(ms);
            ms.Position = 0;

            return ProcessArchiveInternal(ms, fileName, skips, format.Value);
        }

        await using var tmpFileStream = new FileStream(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
        await inputStream.CopyToAsync(tmpFileStream);
        tmpFileStream.Position = 0;

        return ProcessArchiveInternal(tmpFileStream, fileName, skips, format.Value);
    }

    private FileContentResult ProcessArchiveInternal(Stream ms, string fileName, string? skips, OutputArchiveFormat format)
    {
        var (direct, convert) = filter.Filter(archiver.ReadArchive(ms), skips);

        var hasError = false;

        var pdfFiles = convert.Where(p => p.PathAndName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)).ToArray();
        if (pdfFiles.Length != 0)
        {
            foreach (var pdfFile in pdfFiles)
            {
                var pdfImagesToWebp = pdf.ReadImages(pdfFile.Content, fileTime: pdfFile.FileTimeTuple).ToList();
                hasError |= (!converter.ConvertToWebp(pdfImagesToWebp));

                var pdfToWebpEntry = new ArchiveEntry { FileTimeTuple = pdfFile.FileTimeTuple };

                switch (format)
                {
                    case OutputArchiveFormat.Fast7Z:
                        pdfToWebpEntry.Content = archiver.MakeArchiveFast7Z(pdfImagesToWebp);
                        pdfToWebpEntry.PathAndName = pdfFile.PathAndName + "-webp.7z";
                        break;

                    case OutputArchiveFormat.FastZip:
                        pdfToWebpEntry.Content = archiver.MakeArchiveFastZip(pdfImagesToWebp);
                        pdfToWebpEntry.PathAndName = pdfFile.PathAndName + "-webp.zip";
                        break;

                    default:
                        throw new UserFriendlyException($"Invalid {nameof(OutputArchiveFormat)}");
                }

                convert.Remove(pdfFile);
                direct.Add(pdfToWebpEntry);
            }
        }

        hasError |= (!converter.ConvertToWebp(convert));

        direct.AddRange(convert);

        return OutputAsArchive(fileName, format, direct, hasError);
    }

    private FileContentResult OutputAsArchive(string filename, OutputArchiveFormat format, List<ArchiveEntry> direct, bool withErrors)
    {
        return format switch
        {
            OutputArchiveFormat.Fast7Z => File(archiver.MakeArchiveFast7Z(direct), "application/x-7z-compressed", $"{filename}-webp{(withErrors ? "-withErrors" : "")}.7z"),
            OutputArchiveFormat.FastZip => File(archiver.MakeArchiveFastZip(direct), "application/zip", $"{filename}-webp{(withErrors ? "" : "-withErrors")}.zip"),
            _ => throw new UserFriendlyException($"Invalid {nameof(OutputArchiveFormat)}")
        };
    }

    public enum OutputArchiveFormat
    {
        Fast7Z,
        FastZip
    }
}