using QYZH.InteractiveMagazine.Common.Extensions;
using QYZH.InteractiveMagazine.Infrastructure.OSS;
using QYZH.InteractiveMagazine.Models.Dto.Journal;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Models.Enum;
using SqlSugar;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Text.Json;

namespace QYZH.InteractiveMagazine.PrintWorker.Consumers;

/// <summary>
/// 自动铺码消费者。
/// </summary>
public class AutoDotCodeConsumer(
    IConfiguration configuration,
    IServiceScopeFactory scopeFactory,
    IHostEnvironment hostEnvironment,
    ILogger<AutoDotCodeConsumer> logger,
    IHttpClientFactory httpClientFactory,
    OssService ossService) : IQueueConsumer
{
    /// <summary>
    /// 交换机名称。
    /// </summary>
    public string Exchange => "ex.journal";

    /// <summary>
    /// 队列名称。
    /// </summary>
    public string QueueName => "mq.journal.dotcode.auto";

    /// <summary>
    /// 路由键。
    /// </summary>
    public string RoutingKey => "rk.journal.dotcode.auto";

    /// <summary>
    /// 处理自动铺码消息。
    /// </summary>
    public async Task HandleAsync(byte[] body, CancellationToken cancellationToken = default)
    {
        var message = Encoding.UTF8.GetString(body);
        logger.LogInformation("收到自动铺码消息: {Message}", message);

        using var scope = scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();

        try
        {
            var request = JsonSerializer.Deserialize<JournalPagePrintDto>(message, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            });
            if (request == null)
            {
                return;
            }

            var workDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "JournalPagePdf");
            var uploadDirectory = Path.Combine(workDirectory, "upload");
            var downloadDirectory = Path.Combine(workDirectory, "download");
            Directory.CreateDirectory(uploadDirectory);
            Directory.CreateDirectory(downloadDirectory);

            var uploadFileName = $"upload_{request.JournalId}_{DateTime.Now:yyyyMMddHHmmssffffff}{Random.Shared.Next(1000, 9999)}.pdf";
            var uploadFilePath = Path.Combine(uploadDirectory, uploadFileName);

            var journalPdfKey = request.JournalPdfUrl?.RemoveDomain();
            if (string.IsNullOrWhiteSpace(journalPdfKey))
            {
                logger.LogError("书籍上传 PDF 文件地址为空，JournalId: {JournalId}", request.JournalId);
                return;
            }

            await using var pdfStream = ossService.GetObjectStream(journalPdfKey);
            if (pdfStream == null)
            {
                logger.LogError("获取书籍上传 PDF 文件失败，OSS Key: {OssKey}", journalPdfKey);
                return;
            }

            await using (var fs = new FileStream(uploadFilePath, FileMode.CreateNew, FileAccess.Write))
            {
                await pdfStream.CopyToAsync(fs, cancellationToken);
                logger.LogInformation("书籍 PDF 已下载到本地，长度：{Length}", fs.Length);
            }

            var dotId = configuration.GetValue("PrintConfig:DotId", 765837655859269);
            var dotFile = await dbContext.Queryable<DotFile>().FirstAsync(x => x.Id == dotId, cancellationToken);
            if (dotFile == null)
            {
                logger.LogError("配置打印数据错误，点阵文件不存在，DotId: {DotId}", dotId);
                return;
            }

            var printToolDirectory = Path.Combine(hostEnvironment.ContentRootPath, "PrintToolV2.7");
            var exePath = Path.Combine(printToolDirectory, "PrintTool.exe");
            if (!File.Exists(exePath))
            {
                logger.LogError("PrintTool.exe 不存在，路径：{ExePath}", exePath);
                await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request), cancellationToken);
                return;
            }

            var xmlPath = Path.Combine(printToolDirectory, dotFile.FileName);
            if (!File.Exists(xmlPath))
            {
                logger.LogError("铺码授权文件不存在，路径：{XmlPath}", xmlPath);
                await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request), cancellationToken);
                return;
            }

            var downloadFileName = $"download_{request.JournalId}_{DateTime.Now:yyyyMMddHHmmssffffff}{Random.Shared.Next(1000, 9999)}_dot.pdf";
            var downloadFilePath = Path.Combine(downloadDirectory, downloadFileName);
            var dPrint = configuration.GetValue("PrintConfig:DPrint", 0);
            var pageNumMax = request.PageNum.Max(x => x);
            var dotFileDetailList = await dbContext.Queryable<DotFileDetail>()
                .Where(x => x.DotId == dotId && !x.IsUse)
                .OrderBy(x => x.Id)
                .Take(pageNumMax)
                .ToListAsync(cancellationToken);

            if (dotFileDetailList.Count < pageNumMax)
            {
                logger.LogError("点阵页码余量不足，DotId: {DotId}, Need: {Need}, Available: {Available}", dotId, pageNumMax, dotFileDetailList.Count);
                await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request), cancellationToken);
                return;
            }

            var dotFileDetailPageName = dotFileDetailList.Select(x => x.PageName).OrderBy(x => x).ToArray();
            var pageStr = "{" + $"[{dotFileDetailList[0].PageName},{pageNumMax}]" + "}";
            var arguments = $"-sMode=Generate -sPDF=\"{uploadFilePath}\" -sLIC=\"{xmlPath}\" -pStart=1 -oPDF=\"{downloadFilePath}\" -dPageAddr=1 -dPrint={dPrint}  -dDotSize=40 -dType=0 -dOutFile=0 -dControlPageNum={pageStr}";
            var printResult = await ExecutePrintToolAsync(exePath, printToolDirectory, arguments, cancellationToken);

            if (printResult.Timeout || printResult.ExitCode != 0)
            {
                logger.LogError("执行 PrintTool.exe 失败，退出码：{ExitCode}，错误信息：{ErrorMessage}", printResult.ExitCode, printResult.Error);
                await ExecuteUpdateJournalStatusAsync(BuildFailResponse(request, dotFileDetailPageName), cancellationToken);
                return;
            }

            var statusModel = new JournalPagePrintDto
            {
                JournalId = request.JournalId,
                PageNo = dotFileDetailPageName
            };

            if (!ValidatePrintOutput(printResult.Output, dotFileDetailPageName, dPrint))
            {
                statusModel.Status = JournalStatusEnum.CodeFail;
                await ExecuteUpdateJournalStatusAsync(statusModel, cancellationToken);
                return;
            }

            var tempOssDownloadKey = "journal/download/" + downloadFileName;
            const int bufferSize = 1 * 1024 * 1024;
            await using var downloadFs = new FileStream(downloadFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan | FileOptions.Asynchronous);
            var downloadBookPagePdfName = ossService.PutObject(tempOssDownloadKey, downloadFs);
            var ossDomain = configuration.GetSection("AliyunOSSConfigs:Domain").Get<string>() ?? string.Empty;

            statusModel.DownloadJournalPagePdfName = ossDomain + downloadBookPagePdfName;
            await downloadFs.DisposeAsync();

            TryDeleteFile(uploadFilePath);
            TryDeleteFile(downloadFilePath);

            statusModel.Status = JournalStatusEnum.CodeSuccess;
            var callbackResponse = await ExecuteUpdateJournalStatusAsync(statusModel, cancellationToken);
            if (callbackResponse.IsSuccess)
            {
                await dbContext.Ado.UseTranAsync(async () =>
                {
                    foreach (var dotFileDetail in dotFileDetailList)
                    {
                        dotFileDetail.IsUse = true;
                        dotFileDetail.UpdatedAt = DateTime.Now;
                    }

                    await dbContext.Updateable(dotFileDetailList).ExecuteCommandAsync();
                    await dbContext.Updateable<DotFile>()
                        .SetColumns(x => x.TotalUse == x.TotalUse + dotFileDetailList.Count)
                        .SetColumns(x => x.UpdatedAt == DateTime.Now)
                        .Where(x => x.Id == dotId)
                        .ExecuteCommandAsync();
                });
            }
            else
            {
                logger.LogError("回调接口修改书籍状态失败，JournalId: {JournalId}，接口返回消息：{Message}", statusModel.JournalId, callbackResponse.Message);
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "自动铺码处理失败");
            throw;
        }
    }

    /// <summary>
    /// 处理消费异常。
    /// </summary>
    public Task OnErrorAsync(byte[] message, Exception exception)
    {
        var body = Encoding.UTF8.GetString(message);
        logger.LogError(exception, "处理自动铺码消息失败: {Message}", body);
        return Task.CompletedTask;
    }

    private async Task<PrintToolResult> ExecutePrintToolAsync(string exePath, string workingDirectory, string arguments, CancellationToken cancellationToken)
    {
        var printToolTimeoutSeconds = configuration.GetValue("PrintConfig:TimeoutSeconds", 300);
        var printToolTimeout = TimeSpan.FromSeconds(printToolTimeoutSeconds);
        var cmd = $"\"{exePath}\" {arguments}";
        logger.LogInformation("执行 PrintTool.exe 的命令: {Command}，超时时间：{TimeoutSeconds} 秒", cmd, printToolTimeoutSeconds);

        using var process = new Process();
        process.StartInfo = new ProcessStartInfo
        {
            WorkingDirectory = workingDirectory,
            FileName = exePath,
            Arguments = arguments,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true
        };

        process.Start();
        var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
        var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);
        var timeout = false;

        try
        {
            await process.WaitForExitAsync(cancellationToken).WaitAsync(printToolTimeout, cancellationToken);
        }
        catch (TimeoutException)
        {
            timeout = true;
            logger.LogError("执行 PrintTool.exe 超时，准备终止进程树，超时时间：{TimeoutSeconds} 秒，命令：{Command}", printToolTimeoutSeconds, cmd);
            process.Kill(entireProcessTree: true);
            await process.WaitForExitAsync(CancellationToken.None);
        }

        var output = await outputTask;
        var error = await errorTask;
        var exitCode = process.ExitCode;

        logger.LogInformation("执行 PrintTool.exe 后，退出码：{ExitCode}，stdout：{Output}", exitCode, output);
        if (!string.IsNullOrWhiteSpace(error))
        {
            logger.LogWarning("执行 PrintTool.exe 后，stderr：{ErrorMessage}", error);
        }

        return new PrintToolResult(output, error, exitCode, timeout);
    }

    private bool ValidatePrintOutput(string output, string[] dotFileDetailPageName, int dPrint)
    {
        if (string.IsNullOrWhiteSpace(output))
        {
            logger.LogInformation("执行铺码程序没有任何输出");
            return false;
        }

        if (dPrint == 0)
        {
            var pageNoList = JsonSerializer.Deserialize<string[]>(output) ?? [];
            return pageNoList.OrderBy(x => x).SequenceEqual(dotFileDetailPageName.OrderBy(x => x), StringComparer.Ordinal);
        }

        return dotFileDetailPageName.AsParallel().Any(ip => output.Contains(ip, StringComparison.Ordinal));
    }

    private async Task<CallbackUpdateJournalStatusResponse> ExecuteUpdateJournalStatusAsync(JournalPagePrintDto request, CancellationToken cancellationToken)
    {
        var client = httpClientFactory.CreateClient();
        var callbackUrl = configuration.GetValue<string>("PrintConfig:CallBackApiUrl");
        var callbackRequest = new HttpRequestMessage(HttpMethod.Post, callbackUrl)
        {
            Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")
        };

        var response = await client.SendAsync(callbackRequest, cancellationToken);
        if (response.StatusCode == HttpStatusCode.OK)
        {
            var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
            logger.LogInformation("回调接口返回内容：{ResponseContent}", responseContent);
            return JsonSerializer.Deserialize<CallbackUpdateJournalStatusResponse>(responseContent, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            }) ?? new CallbackUpdateJournalStatusResponse();
        }

        return new CallbackUpdateJournalStatusResponse();
    }

    private static JournalPagePrintDto BuildFailResponse(JournalPagePrintDto request, string[]? pageNo = null)
    {
        return new JournalPagePrintDto
        {
            JournalId = request.JournalId,
            PageNo = pageNo ?? [],
            Status = JournalStatusEnum.CodeFail
        };
    }

    private void TryDeleteFile(string filePath)
    {
        try
        {
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "删除临时文件失败，路径：{FilePath}", filePath);
        }
    }
}

internal sealed record PrintToolResult(string Output, string Error, int ExitCode, bool Timeout);

internal class CallbackUpdateJournalStatusResponse
{
    public string? Message { get; set; }

    public object? Code { get; set; }

    public bool Result { get; set; }

    public bool IsSuccess { get; set; }
}
