using System.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using SolutionErp.Application.Common.Interfaces;

namespace SolutionErp.Infrastructure.Forms;

// Shells out to LibreOffice headless for arbitrary .docx/.doc/.xlsx/.xls →
// pdf/docx conversion. Requires soffice.exe installed on the host. Config
// Pdf:SofficePath overrides default path for cross-platform dev.
public class LibreOfficeDocumentConverter(
    IConfiguration config,
    ILogger<LibreOfficeDocumentConverter> logger) : IDocumentConverter
{
    private readonly string _sofficePath = config["Pdf:SofficePath"]
        ?? (OperatingSystem.IsWindows()
            ? @"C:\Program Files\LibreOffice\program\soffice.exe"
            : "soffice");

    private readonly TimeSpan _timeout = TimeSpan.FromSeconds(
        config.GetValue<int?>("Pdf:TimeoutSeconds") ?? 60);

    public async Task<byte[]> ConvertAsync(byte[] sourceBytes, string sourceExt, string targetExt, CancellationToken ct = default)
    {
        var srcExt = sourceExt.TrimStart('.').ToLowerInvariant();
        var tgtExt = targetExt.TrimStart('.').ToLowerInvariant();
        if (srcExt is not ("docx" or "xlsx" or "doc" or "xls"))
            throw new InvalidOperationException($"Unsupported source extension: {srcExt}");
        if (tgtExt is not ("pdf" or "docx" or "xlsx"))
            throw new InvalidOperationException($"Unsupported target extension: {tgtExt}");

        // Per-conversion temp directory — avoids filename collisions when
        // multiple requests run concurrently and makes cleanup atomic.
        var workDir = Path.Combine(Path.GetTempPath(), "solutionerp-conv", Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(workDir);
        var inputName = $"input.{srcExt}";
        var inputPath = Path.Combine(workDir, inputName);
        var outputPath = Path.Combine(workDir, $"input.{tgtExt}");

        try
        {
            await File.WriteAllBytesAsync(inputPath, sourceBytes, ct);

            var psi = new ProcessStartInfo
            {
                FileName = _sofficePath,
                // --headless: no GUI
                // --norestore: don't try to resurrect prior session
                // -env:UserInstallation: isolate user profile per worker (avoids
                //   "soffice already running" conflict when concurrent)
                Arguments = $"--headless --norestore --convert-to {tgtExt} " +
                            $"-env:UserInstallation=file:///{workDir.Replace('\\', '/')}/profile " +
                            $"--outdir \"{workDir}\" \"{inputPath}\"",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                CreateNoWindow = true,
                WorkingDirectory = workDir,
            };

            using var proc = new Process { StartInfo = psi };
            proc.Start();

            using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
            cts.CancelAfter(_timeout);

            try
            {
                await proc.WaitForExitAsync(cts.Token);
            }
            catch (OperationCanceledException)
            {
                try { proc.Kill(entireProcessTree: true); } catch { /* best effort */ }
                throw new TimeoutException($"LibreOffice conversion exceeded {_timeout.TotalSeconds}s.");
            }

            if (proc.ExitCode != 0)
            {
                var stderr = await proc.StandardError.ReadToEndAsync(ct);
                var stdout = await proc.StandardOutput.ReadToEndAsync(ct);
                logger.LogError("soffice {Src}→{Tgt} exit {Code}. stderr={Stderr} stdout={Stdout}",
                    srcExt, tgtExt, proc.ExitCode, stderr, stdout);
                throw new InvalidOperationException($"LibreOffice conversion failed (exit {proc.ExitCode}).");
            }

            if (!File.Exists(outputPath))
                throw new InvalidOperationException($"LibreOffice did not produce {tgtExt} output.");

            return await File.ReadAllBytesAsync(outputPath, ct);
        }
        finally
        {
            try { Directory.Delete(workDir, recursive: true); } catch { /* best effort */ }
        }
    }
}
