namespace Fast.Shared.Logic.Package;

using System.Net.Http.Headers;
using Fast.Shared.Logic.FileService;

/// Converts Office documents (DOCX, XLSX, PPTX) to PDF using a Gotenberg service request.
/// Uses FileService for storage-agnostic file operations (local or SharePoint).
public static class PdfConverter
{
    private class GotenbergClientOptions
    {
        public const string SectionName = "GotenbergSharpClient";

        public string ServiceUrl { get; set; } = "http://cloud.implefast.com:3000";
    }

    /// Converts an Office file (DOCX, XLSX, PPTX) to a PDF file using FileService.
    public static async Task ConvertAsync(
        IFileService fileService,
        string inputFolderName,
        string inputFileName,
        string outputFolderName,
        string outputFileName,
        bool useSharding = false)
    {
        // Read input file via FileService
        var fileBytes = await fileService.ReadAllBytesAsync(inputFolderName, inputFileName, useSharding);
        using var ms = new MemoryStream(fileBytes);
        ms.Position = 0;

        // Convert to PDF
        var pdfStream = await ConvertToStream(ms, inputFileName);
        var pdfBytes = pdfStream.ToArray();

        // Write output via FileService
        await fileService.WriteAllBytesAsync(outputFolderName, outputFileName, pdfBytes, useSharding);

        Console.WriteLine($"Successfully converted: {inputFileName} → {outputFileName}");
    }

    /// Converts a DOCX stream to a PDF and saves via FileService.
    public static async Task ConvertDocxAsync(
        MemoryStream docxStream,
        IFileService fileService,
        string outputFolderName,
        string outputFileName,
        bool useSharding = false)
    {
        var randomFileName = $"temp-{Util.String.GetNewGuid()}.docx";
        var pdfStream = await ConvertToStream(docxStream, randomFileName);
        var pdfBytes = pdfStream.ToArray();

        await fileService.WriteAllBytesAsync(outputFolderName, outputFileName, pdfBytes, useSharding);
        Console.WriteLine($"Successfully converted DOCX to PDF: {outputFileName}");
    }

    /// Converts a XLSX stream to a PDF and saves via FileService.
    public static async Task ConvertXlsxAsync(
        MemoryStream xlsxStream,
        IFileService fileService,
        string outputFolderName,
        string outputFileName,
        bool useSharding = false)
    {
        var randomFileName = $"temp-{Util.String.GetNewGuid()}.xlsx";
        var pdfStream = await ConvertToStream(xlsxStream, randomFileName);
        var pdfBytes = pdfStream.ToArray();

        await fileService.WriteAllBytesAsync(outputFolderName, outputFileName, pdfBytes, useSharding);
        Console.WriteLine($"Successfully converted XLSX to PDF: {outputFileName}");
    }

    /// Converts an Office document (DOCX, XLSX, PPTX) MemoryStream to a PDF MemoryStream.
    public static async Task<MemoryStream> ConvertToStream(MemoryStream documentStream, string inputFileName)
    {
        var options = LoadConfiguration();
        var convertUrl = $"{options.ServiceUrl.TrimEnd('/')}/forms/libreoffice/convert";

        using var httpClient = new HttpClient();
        httpClient.Timeout = TimeSpan.FromSeconds(15);

        using var form = new MultipartFormDataContent();
        var fileBytes = documentStream.ToArray();

        var mimeType = GetMimeType(inputFileName);

        var fileContent = new ByteArrayContent(fileBytes);
        fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);

        form.Add(fileContent, "files", inputFileName);

        HttpResponseMessage response;
        try
        {
            response = await httpClient.PostAsync(convertUrl, form);
            response.EnsureSuccessStatusCode();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error converting file: {ex.Message}");
            throw;
        }

        var pdfBytes = await response.Content.ReadAsByteArrayAsync();
        var pdfStream = new MemoryStream(pdfBytes);
        pdfStream.Position = 0;

        Console.WriteLine($"Successfully converted: {inputFileName} to PDF");
        return pdfStream;
    }

    /// Merges multiple PDF files into a single PDF MemoryStream using FileService.
    public static async Task<MemoryStream> MergePdfsAsync(
        IFileService fileService,
        IEnumerable<(string folderName, string fileName)> pdfFiles,
        bool useSharding = false)
    {
        if (pdfFiles == null || !pdfFiles.Any())
        {
            throw new ArgumentException("The list of PDF files cannot be null or empty.", nameof(pdfFiles));
        }

        var pdfBytesList = new List<(string fileName, byte[] bytes)>();
        foreach (var (folderName, fileName) in pdfFiles)
        {
            var fileBytes = await fileService.ReadAllBytesAsync(folderName, fileName, useSharding);
            pdfBytesList.Add((fileName, fileBytes));
        }

        return await MergePdfsFromBytes(pdfBytesList);
    }

    /// Merges multiple PDF files into a single PDF and saves via FileService.
    public static async Task MergePdfsAndSaveAsync(
        IFileService fileService,
        IEnumerable<(string folderName, string fileName)> pdfFiles,
        string outputFolderName,
        string outputFileName,
        bool useSharding = false)
    {
        var mergedPdfStream = await MergePdfsAsync(fileService, pdfFiles, useSharding);
        var pdfBytes = mergedPdfStream.ToArray();

        await fileService.WriteAllBytesAsync(outputFolderName, outputFileName, pdfBytes, useSharding);
        Console.WriteLine($"Successfully merged {pdfFiles.Count()} PDF(s) and saved to: {outputFileName}");
    }

    /// Merges multiple PDF byte arrays into a single PDF MemoryStream using the Gotenberg service.
    public static async Task<MemoryStream> MergePdfsFromBytes(IEnumerable<(string fileName, byte[] bytes)> pdfBytesList)
    {
        if (pdfBytesList == null || !pdfBytesList.Any())
        {
            throw new ArgumentException("The list of PDF data cannot be null or empty.", nameof(pdfBytesList));
        }

        var options = LoadConfiguration();
        var mergeUrl = $"{options.ServiceUrl.TrimEnd('/')}/forms/pdfengines/merge";

        using var httpClient = new HttpClient();
        httpClient.Timeout = TimeSpan.FromSeconds(30); // Merging might take longer

        using var form = new MultipartFormDataContent();

        foreach (var (fileName, fileBytes) in pdfBytesList)
        {
            var fileContent = new ByteArrayContent(fileBytes);
            fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
            form.Add(fileContent, "files", fileName); // "files" is the field name Gotenberg expects
        }

        HttpResponseMessage response;
        try
        {
            response = await httpClient.PostAsync(mergeUrl, form);
            response.EnsureSuccessStatusCode();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error merging PDF files: {ex.Message}");
            throw;
        }

        // Read the PDF bytes from the response
        var pdfBytes = await response.Content.ReadAsByteArrayAsync();
        Console.WriteLine($"Successfully merged {pdfBytesList.Count()} PDF(s) into a MemoryStream.");

        // Return a new MemoryStream initialized with the merged PDF bytes
        return new MemoryStream(pdfBytes);
    }

    /// Determines the correct MIME type for supported Office formats, based on extension.
    private static string GetMimeType(string fileName)
    {
        var ext = Path.GetExtension(fileName).ToLowerInvariant();
        return ext switch
        {
            ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
            _ => "application/octet-stream"
        };
    }

    /// Attempts to load config from 'appsettings.json'.
    /// If it can't, then it uses default values.
    private static GotenbergClientOptions LoadConfiguration()
    {
        const string appSettingsFileName = "appsettings.json";

        var basePath = Directory.GetCurrentDirectory();
        string[] possiblePaths =
        {
            Path.Combine(basePath, appSettingsFileName), // this path used when deployed
            Path.Combine(basePath, "..", "Fast.Shared", appSettingsFileName) // this path used when in development
        };

        var configPath = possiblePaths.FirstOrDefault(File.Exists);

        if (configPath == null)
        {
            Console.WriteLine($"Warning: {appSettingsFileName} not found. Using default Gotenberg configuration.");
            // return a new instance with default values set in the class.
            return new GotenbergClientOptions();
        }

        var configuration = new ConfigurationBuilder()
            .AddJsonFile(configPath, optional: false, reloadOnChange: true)
            .Build();

        var options = new GotenbergClientOptions();
        var configSection = configuration.GetSection(GotenbergClientOptions.SectionName);

        if (!configSection.Exists())
        {
            Console.WriteLine(
                $"Warning: Configuration section '{GotenbergClientOptions.SectionName}' "
                + $"not found in {appSettingsFileName}. Using default Gotenberg configuration."
            );
            // if the section doesn't exist, use default values.
            return options;
        }

        // if the section exists, bind its values. this will overwrite
        // the defaults with any values specified in appsettings.json.
        configSection.Bind(options);

        return options;
    }
}
