using Newtonsoft.Json;
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.Threading.Channels;

namespace QYZH.InteractiveMagazine.WorkService.Consumers;

/// <summary>
/// 自动铺码消费者
/// </summary>
public class AutoDotCodeConsumer(IConfiguration configuration,
    IServiceScopeFactory scopeFactory,
    IWebHostEnvironment webHostEnvironment,
    ILogger<AutoDotCodeConsumer> logger,
    IHttpClientFactory httpClientFactory,
    OssService ossService
    ) : IQueueConsumer
{

    public string Exchange => "ex.journal";

    public string QueueName => "mq.journal.dotcode.auto";

    public string RoutingKey => "rk.journal.dotcode.auto";

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

        // TODO: 在此编写具体的铺码处理逻辑
        using var scope = scopeFactory.CreateScope();
        var dBContext = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();

        try
        {
            var journalPagePrintDtoMessage = JsonConvert.DeserializeObject<JournalPagePrintDto>(message);
            if (journalPagePrintDtoMessage == null)
                return;

            await dBContext.Ado.BeginTranAsync();

            #region 创建文件夹以及下载PDF文件

            var currentDomainDic = AppDomain.CurrentDomain.BaseDirectory + "JournalPagePdf";
            if (!Directory.Exists(currentDomainDic))
                Directory.CreateDirectory(currentDomainDic);

            var uploadPdfDic = currentDomainDic + "/upload/";
            if (!Directory.Exists(uploadPdfDic))
                Directory.CreateDirectory(uploadPdfDic);

            var downloadDic = currentDomainDic + "/download/";
            if (!Directory.Exists(downloadDic))
                Directory.CreateDirectory(downloadDic);

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

            var uploadFilePath = uploadPdfDic + uploadFileName;

            // 获取上传成功的书籍页码pdf文件
            var journalPdfKey = journalPagePrintDtoMessage.JournalPdfUrl?.RemoveDomain();
            if (string.IsNullOrWhiteSpace(journalPdfKey))
            {
                logger.LogError("书籍上传的PDF文件地址为空，JournalId: {JournalId}", journalPagePrintDtoMessage.JournalId);
                return;
            }

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

            await using (var fs = new FileStream(uploadFilePath, FileMode.CreateNew, FileAccess.Write))
            {
                await pdfSteam.CopyToAsync(fs, cancellationToken);
                logger.LogInformation($" 获取书籍上传的PDF文件成功下载到本地，长度为：{fs.Length}");
            }

            #endregion


            #region 调用铺码程序

            // 从配置中获取点阵文件Id
            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}");
                return;
            }

            var printToolDirectory = Path.Combine(webHostEnvironment.ContentRootPath, "PrintToolV2.7");
            var exePath = Path.Combine(printToolDirectory, "PrintTool.exe");

            var xmlPath = Path.Combine(printToolDirectory, dotfile.FileName);


            #region 注释说明

            //1、-sMode = Generate （必须）
            //2、-sPDF = 源文件pdf格式的完整路径（必须）
            //3、-sLIC = 铺码资源文件完整路径（必须）
            //4、-oPDF = 生成pdf文件的完整路径（必须）
            //5、-dType = 点阵形状，0：方点，1：圆点，默认为0（可选）
            //6、-dPrint = 打印场景，0：普通激光打印机，1：工业印刷，默认为0（可选）
            //7、-pStart = 数字，整数，可以制定资源文件从第几个编号开始铺码（非必须，默认0，表示从资源的剩余页码开始，每次铺码成功后点阵资源会相应减少；大于0时点阵资源不会减少，pStart = 1时表示从第一页开始铺码）
            //8、-dPageAddr = 是否显示点阵页码地址，0：不显示，1：显示，默认为0（可选）
            //9、-sPrinter = 打印机名称（必须）
            //10、-dPageStart = 数字，设置打印起始页码，1表示从第一页开始打印，0：全部打印，默认0（可选）
            //11、-dPageEnd = 数字，设置打印结束页码，0：全部打印，默认0（可选）
            //12、-dCopy = 数字，设置打印份数，默认1（可选）
            //13、-dKValue = 数字，设置码点颜色深度，取值范围50 - 100，默认100，比如取值90表示k值为90 %（可选）
            //14、-dDotSize = 数字，设置码点大小，取值范围30 - 50，默认40（可选）,方点仅支持40
            //15、-dOutFile = 数字，0：只生成带点阵pdf文件，1：只生成纯点阵文件，2：既生成纯点阵文件也生成带点阵pdf文件，默认为0（可选）

            //16、-dControlPageNum ={ [页地址, 连续数量],[页地址, 连续数量]...}
            //可一个pdf有多段页码段，默认从第一页开始(可选)（最后一段若是不想数pdf剩下多少页，可直接放0默认用最后字段铺完剩下的页）

            //说明：
            //当 -dOutFile = 2时，纯点阵文件名为输入的 - oPDF参数，带点阵pdf文件名为在 - oPDF参数后加上"_dp"，即"D:\pdf\28_dot_dp.pdf"。

            //示例：
            //制作点阵：
            //-sMode=Generate -sPDF="D:\pdf\28.pdf" -sLIC="D:\Root licnese segment 70_70.0.0.0_100.xml" -oPDF="D:\pdf\28_dot.pdf" -pStart=1

            //制作纯点阵文件：
            //-sMode=Generate -sPDF="D:\pdf\28.pdf" -sLIC="D:\Root licnese segment 70_70.0.0.0_100.xml" -oPDF="D:\pdf\28_dot.pdf" -dOutFile=1 

            //打印：
            //-sMode=Print -sPDF="D:\pdf\28_dot.pdf" -sPrinter="HP LaserJet Professional M1216nfh MFP (副本 1)" -dPageStart=2 -dPageEnd=3 -dCopy=5

            #endregion

            var downloadFileName = $"download_{journalPagePrintDtoMessage.JournalId}_{DateTime.Now:yyyyMMddHHmmssffffff}{Random.Shared.Next(1000, 9999)}_dot.pdf";

            // 生成成功的PDF文件路径
            var downloadFilePath = downloadDic + downloadFileName;

            // -dPrint = 打印场景，0：普通激光打印机，1：工业印刷，默认为0（可选）
            var dPrint = configuration.GetValue("PrintConfig:DPrint", 0);

            // 获取书籍页码中的最大页数，作为铺码程序需要铺的页数（连续数量）
            var pageNumMax = journalPagePrintDtoMessage.PageNum.Max(x => x);

            // 根据点阵文件Id获取对应的页码详情列表，按照Id升序排序，取前N条（N为书籍页数）
            var dotFileDetailList = await dBContext.Queryable<DotFileDetail>().Where(x => x.DotId == dotId && !x.IsUse).OrderBy(x => x.Id).Take(pageNumMax).ToListAsync(cancellationToken);

            // 获取页码详情列表中的页地址，组成一个数组
            var dotFileDetailPageName = dotFileDetailList.Select(x => x.PageName).OrderBy(x => x).ToArray();

            // -dControlPageNum ={ [页地址, 连续数量],[页地址, 连续数量]...}
            //  string pageStr = "{" + string.Join(",", item.Pages.Select(s => $"[{s.PageAddress},{s.PageNum}]")) + "}";

            // 从第一个开始执行，连续铺码N条（N为书籍页数）
            string 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 cmd = $"\"{exePath}\" {arguments}";

            logger.LogInformation($"执行PrintTool.exe 的命令: {cmd}");

            string output;
            using (var p = new Process())
            {
                p.StartInfo = new ProcessStartInfo
                {
                    WorkingDirectory = printToolDirectory,
                    FileName = exePath,
                    Arguments = arguments,
                    UseShellExecute = false,       //是否使用操作系统shell启动
                    RedirectStandardInput = true,  //接受来自调用程序的输入信息
                    RedirectStandardOutput = true, //由调用程序获取输出信息
                    RedirectStandardError = true,  //重定向标准错误输出
                    CreateNoWindow = true,         //不显示程序窗口
                };

                p.Start();
                output = await p.StandardOutput.ReadToEndAsync(cancellationToken);

                logger.LogInformation("执行 ProcessStartInfo 执行命令后，output 输出值：{Output}", output);

                var exeErrorMsg = await p.StandardError.ReadToEndAsync(cancellationToken);
                if (!string.IsNullOrWhiteSpace(exeErrorMsg))
                {
                    logger.LogInformation($"执行 ProcessStartInfo 执行命令后，返回的错误信息为：{exeErrorMsg}");
                }

                await p.WaitForExitAsync(cancellationToken);
                p.Kill();
            }
            logger.LogInformation("执行 PrintTool.exe 文件成功");

            #endregion

            var journalPagePrintDtoModel = new JournalPagePrintDto
            {
                JournalId = journalPagePrintDtoMessage.JournalId,
                PageNo = dotFileDetailPageName,
            };

            #region 根据配置的dPrint 验证铺码程序执行的结果 output数据

            if (string.IsNullOrWhiteSpace(output))
            {
                logger.LogInformation("执行铺码程序没有任何输出，请联系管理员");
                journalPagePrintDtoModel.Status = JournalStatusEnum.CodeFail;
                await ExecuteUpdateJournalStatus(journalPagePrintDtoModel, dBContext);
                return;
            }

            // 如果是激光打印场景，output肯定和 dotFileDetailPageName 是一一对应的关系，如果是工业印刷场景，output可能会有其他信息，所以需要验证output中是否包含dotFileDetailPageName中的页码
            if (dPrint == 0)
            {
                var pageNoList = JsonConvert.DeserializeObject<string[]>(output) ?? [];
                var isequalArray = pageNoList.OrderBy(x => x).SequenceEqual(dotFileDetailPageName.OrderBy(x => x), StringComparer.Ordinal);
                if (!isequalArray)
                {
                    logger.LogInformation("执行铺码程序输出的页码与期望的页码不一致，请联系管理员");
                    journalPagePrintDtoModel.Status = JournalStatusEnum.CodeFail;
                    await ExecuteUpdateJournalStatus(journalPagePrintDtoModel, dBContext);
                    return;
                }

            }
            else if (dPrint == 1)// 如果是工业印刷场景，验证页码是否打印过，如果打印过则直接使用原来的页码，不再执行铺码程序
            {
                // 并行验证页码是否存在于铺码程序的输出中，存在则说明打印过，不存在则说明没有打印过，说明铺码程序没有执行成功
                var existPageNo = dotFileDetailPageName.AsParallel().Any(ip => output.Contains(ip, StringComparison.Ordinal));
                if (!existPageNo)
                {
                    logger.LogInformation("执行铺码程序没有任何输出，请联系管理员");
                    journalPagePrintDtoModel.Status = JournalStatusEnum.CodeFail;
                    await ExecuteUpdateJournalStatus(journalPagePrintDtoModel, dBContext);
                    return;
                }
            }

            #endregion

            #region 把本地文集上传到OOS上

            var tempOssDownloadKey = "journal/download/" + downloadFileName;

            const int bufferSize = 1 * 1024 * 1024; // 1MB

            await using var downloadFs = new FileStream(downloadFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan | FileOptions.Asynchronous);

            //把本地文件上传到OOS上
            var downloadBookPagePdfName = ossService.PutObject(tempOssDownloadKey, downloadFs);

            var ossDomain = configuration.GetSection("AliyunOSSConfigs:Domain").Get<string>() ?? string.Empty;

            journalPagePrintDtoModel.DownloadJournalPagePdfName = ossDomain + downloadBookPagePdfName;

            // 这里一定要释放上面的流，否则下面无法删除文件
            await downloadFs.DisposeAsync();

            #endregion

            ////删除临时文件
            File.Delete(uploadFilePath);

            File.Delete(downloadFilePath);

            #region 调用API，成功后修改页码状态为已使用

            journalPagePrintDtoModel.Status = JournalStatusEnum.CodeSuccess;

            // 调用回调接口修改书籍状态为铺码成功
            var callbackResponse = await ExecuteUpdateJournalStatus(journalPagePrintDtoModel, dBContext);

            if (callbackResponse != null && callbackResponse.IsSuccess)
            {
                logger.LogInformation($"回调接口成功修改书籍状态为铺码成功，书籍ID：{journalPagePrintDtoModel.JournalId}");

                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();

                ////手动确认消息已处理（由于下方 autoAck 设为 false）
                //await channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false, cancellationToken: cancellationToken);
            }
            else
            {
                logger.LogError($"回调接口修改书籍状态为铺码成功没有成功，书籍ID：{journalPagePrintDtoModel.JournalId}，接口返回消息：{callbackResponse?.Message}");
            }

            #endregion


            await dBContext.Ado.CommitTranAsync();

        }
        catch (Exception ex)
        {
            await dBContext.Ado.RollbackTranAsync();
            logger.LogError(ex, "铺码错误，回调接口修改书籍状态为铺码失败没有成功");
        }
        await Task.CompletedTask;
    }
    private async Task<CallbackUpdateJournalStatusResponse> ExecuteUpdateJournalStatus(JournalPagePrintDto request, ISqlSugarClient dBContext)
    {
        var client = httpClientFactory.CreateClient();
        var callbackUrl = configuration.GetValue<string>("PrintConfig:CallBackApiUrl");
        var callbackRequest = new HttpRequestMessage(HttpMethod.Post, callbackUrl)
        {
            Content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json")
        };
        var response = await client.SendAsync(callbackRequest);
        if (response.StatusCode == HttpStatusCode.OK)
        {
            var responseContent = await response.Content.ReadAsStringAsync();
            logger.LogInformation($"回调接口成功修改书籍状态为铺码失败，接口返回内容：{responseContent}");
            return JsonConvert.DeserializeObject<CallbackUpdateJournalStatusResponse>(responseContent) ?? new CallbackUpdateJournalStatusResponse();
        }
        return new CallbackUpdateJournalStatusResponse();
    }
    public Task OnErrorAsync(byte[] message, Exception exception)
    {
        var body = Encoding.UTF8.GetString(message);
        logger.LogError(exception, "处理自动铺码消息失败: {Message}", body);
        return Task.CompletedTask;
    }
}
internal class CallbackUpdateJournalStatusResponse
{
    public string? Message { get; set; }

    public string? Code { get; set; }

    public bool Result { get; set; }

    public bool IsSuccess { get; set; }
}
