using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Visualization;

/// <summary>以同级临时文件生成六张 SVG、六张 PNG 和 CSV 后成组发布。</summary>
public sealed class SmoothingReportExporter
{
    private readonly SmoothingFontResolver _fontResolver = new SmoothingFontResolver();
    private readonly SmoothingFigureSetBuilder _figureSetBuilder = new SmoothingFigureSetBuilder();
    private readonly SmoothingSvgRenderer _svgRenderer = new SmoothingSvgRenderer();
    private readonly SmoothingPngRenderer _pngRenderer = new SmoothingPngRenderer();
    private readonly SmoothingCsvWriter _csvWriter = new SmoothingCsvWriter();

    public SmoothingReportExportResult Export(SmoothingReportExportRequest request)
    {
        if (request == null || request.Model == null || string.IsNullOrWhiteSpace(request.OutputDirectory) || !IsFileStem(request.FileStem))
            return SmoothingReportExportResult.Failure(SmoothingReportExportStatus.InvalidInput, "报告模型、输出目录或文件名无效。");
        if (!_fontResolver.TryResolve(request.ChineseFontFamilyName, request.LatinFontFamilyName, out SmoothingFontResolution availableFonts, out string fontReason))
            return SmoothingReportExportResult.Failure(SmoothingReportExportStatus.FontUnavailable, fontReason);
        availableFonts.Dispose();

        var pending = new List<PendingFile>();
        var svgPaths = new List<string>();
        var pngPaths = new List<string>();
        string csvPath = Path.Combine(request.OutputDirectory, request.FileStem + ".csv");
        try
        {
            SmoothingFigureSet figures = _figureSetBuilder.Build(request.Model);
            for (int index = 0; index < figures.Figures.Count; index++)
            {
                SmoothingFigureDefinition figure = figures.Figures[index];
                string svgPath = Path.Combine(request.OutputDirectory, figure.FileStem + ".svg");
                string pngPath = Path.Combine(request.OutputDirectory, figure.FileStem + ".png");
                svgPaths.Add(svgPath);
                pngPaths.Add(pngPath);
                pending.Add(new PendingFile(svgPath, Encoding.UTF8.GetBytes(_svgRenderer.Render(figure))));
                if (!_fontResolver.TryResolve(request.ChineseFontFamilyName, request.LatinFontFamilyName, out SmoothingFontResolution renderFonts, out fontReason))
                    throw new InvalidOperationException(fontReason);
                byte[] png;
                using (renderFonts) png = _pngRenderer.Render(figure, renderFonts);
                pending.Add(new PendingFile(pngPath, png));
            }
            pending.Add(new PendingFile(csvPath, _csvWriter.Write(request.Model)));
            Directory.CreateDirectory(request.OutputDirectory);
            for (int index = 0; index < pending.Count; index++) File.WriteAllBytes(pending[index].TemporaryPath, pending[index].Content);
            PublishAll(pending);
            DeleteIfExists(Path.Combine(request.OutputDirectory, "comparison.svg"));
            DeleteIfExists(Path.Combine(request.OutputDirectory, "comparison.png"));
            return SmoothingReportExportResult.Success(svgPaths, pngPaths, csvPath);
        }
        catch (Exception exception)
        {
            RestorePublishedFiles(pending);
            return SmoothingReportExportResult.Failure(SmoothingReportExportStatus.Failed, exception.Message);
        }
        finally
        {
            for (int index = 0; index < pending.Count; index++)
            {
                DeleteIfExists(pending[index].TemporaryPath);
                DeleteIfExists(pending[index].BackupPath);
            }
        }
    }

    private static void PublishAll(IReadOnlyList<PendingFile> files)
    {
        string transaction = Guid.NewGuid().ToString("N");
        for (int index = 0; index < files.Count; index++)
        {
            PendingFile file = files[index];
            file.ExistedBeforePublish = File.Exists(file.FinalPath);
            file.BackupPath = file.ExistedBeforePublish ? file.FinalPath + ".backup-" + transaction : string.Empty;
            if (file.ExistedBeforePublish) File.Replace(file.TemporaryPath, file.FinalPath, file.BackupPath);
            else File.Move(file.TemporaryPath, file.FinalPath);
            file.Published = true;
        }
        for (int index = 0; index < files.Count; index++) DeleteIfExists(files[index].BackupPath);
    }

    private static void RestorePublishedFiles(IReadOnlyList<PendingFile> files)
    {
        for (int index = files.Count - 1; index >= 0; index--)
        {
            PendingFile file = files[index];
            if (!file.Published) continue;
            try
            {
                if (file.ExistedBeforePublish && File.Exists(file.BackupPath))
                {
                    if (File.Exists(file.FinalPath)) File.Replace(file.BackupPath, file.FinalPath, null);
                    else File.Move(file.BackupPath, file.FinalPath);
                }
                else if (!file.ExistedBeforePublish) DeleteIfExists(file.FinalPath);
            }
            catch { }
        }
    }

    private static bool IsFileStem(string value)
    {
        return !string.IsNullOrWhiteSpace(value) && value.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 && value.IndexOf(Path.DirectorySeparatorChar) < 0 && value.IndexOf(Path.AltDirectorySeparatorChar) < 0;
    }

    private static void DeleteIfExists(string path)
    {
        if (!string.IsNullOrEmpty(path) && File.Exists(path)) File.Delete(path);
    }

    private sealed class PendingFile
    {
        public PendingFile(string finalPath, byte[] content)
        {
            FinalPath = finalPath;
            Content = content;
            TemporaryPath = finalPath + ".tmp";
            BackupPath = string.Empty;
        }

        public string FinalPath { get; }
        public byte[] Content { get; }
        public string TemporaryPath { get; }
        public string BackupPath { get; set; }
        public bool ExistedBeforePublish { get; set; }
        public bool Published { get; set; }
    }
}
