using AutoMapper;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Performance.DtoModels;
using Performance.DtoModels.AppSettings;
using Performance.EntityModels;
using Performance.Infrastructure;
using Performance.Repository;
using Performance.Services.AllotCompute;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Performance.Services
{
    public class AllotService : IAutoInjection
    {
        private BaiscNormService baiscNormService;
        private CheckDataService checkDataService;
        private ImportDataService importDataService;
        private ProcessComputService processComputService;
        private ResultComputeService resultComputeService;
        private PerforLogdbugRepository logdbug;
        private ConfigService configService;
        private IHostingEnvironment _evn;
        private ILogger<AllotService> _logger;
        private PerforPerallotRepository _allotRepository;
        private IEmailService emailService;
        private readonly IOptions<Application> options;
        private PerforPeragainallotRepository _againallotRepository;
        private PerforLogcheckRepository perforLogcheckRepository;
        private readonly PerforHospitalRepository perforHospitalRepository;
        private readonly PerforResbaiscnormRepository perforResbaiscnormRepository;

        //private readonly IHubContext<AllotLogHub> hubContext;
        private readonly LogManageService logManageService;
        private readonly ReportService reportService;

        public AllotService(PerforPerallotRepository allotRepository,
             BaiscNormService baiscNormService,
             CheckDataService checkDataService,
             ImportDataService importDataService,
             ProcessComputService processComputService,
             ResultComputeService resultComputeService,
             ConfigService configService,
             PerforLogdbugRepository logdbug,
             IHostingEnvironment evn, ILogger<AllotService> logger,
             IEmailService emailService,
             IOptions<Application> options,
             PerforPeragainallotRepository againallotRepository,
             PerforLogcheckRepository perforLogcheckRepository,
             PerforHospitalRepository perforHospitalRepository,
             PerforResbaiscnormRepository perforResbaiscnormRepository,
             //IHubContext<AllotLogHub> hubContext
             LogManageService logManageService,
             ReportService reportService)
        {
            _allotRepository = allotRepository;
            _againallotRepository = againallotRepository;
            _logger = logger;
            _evn = evn;
            this.baiscNormService = baiscNormService;
            this.checkDataService = checkDataService;
            this.importDataService = importDataService;
            this.processComputService = processComputService;
            this.resultComputeService = resultComputeService;
            this.emailService = emailService;
            this.options = options;
            this.configService = configService;
            this.logdbug = logdbug;
            this.perforLogcheckRepository = perforLogcheckRepository;
            this.perforHospitalRepository = perforHospitalRepository;
            this.perforResbaiscnormRepository = perforResbaiscnormRepository;
            //this.hubContext = hubContext;
            this.logManageService = logManageService;
            this.reportService = reportService;
        }

        #region 基础功能
        /// <summary>
        /// 绩效记录
        /// </summary>
        /// <param name="hospitalId"></param>
        /// <returns></returns>
        public List<AllotResponse> GetAllotList(int? hospitalId)
        {
            if (!hospitalId.HasValue || hospitalId.Value <= 0)
                throw new PerformanceException("hospitalId无效");
            var allotList = _allotRepository.GetEntities(t => t.HospitalId == hospitalId);
            allotList = allotList == null ? allotList : allotList.OrderByDescending(t => t.ID).ToList();
            var reuslt = Mapper.Map<List<AllotResponse>>(allotList);
            reuslt?.ForEach(t =>
            {
                t.IsDown = !string.IsNullOrEmpty(t.ExtractPath);
                if (!string.IsNullOrEmpty(t.ExtractPath))
                    t.ExtractPath = t.ExtractPath.Replace(options.Value.AbsolutePath, options.Value.HttpPath).Replace("\\", "/");
            });
            return reuslt;
        }

        /// <summary>
        /// 新增
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public per_allot InsertAllot(AllotRequest request, int userID)
        {
            var repAllot = _allotRepository.GetEntities(t => t.HospitalId == request.HospitalId && t.Year == request.Year && t.Month == request.Month);
            if (repAllot != null && repAllot.Count() > 0)
                throw new PerformanceException("当前绩效记录已存在");

            var allot = Mapper.Map<per_allot>(request);
            allot.CreateDate = DateTime.Now;
            allot.CreateUser = userID;
            allot.States = (int)AllotStates.NoData;
            allot.Remark = EnumHelper.GetDescription(AllotStates.NoData);
            if (!_allotRepository.Add(allot))
                throw new PerformanceException("保存失败");

            return allot;
        }

        /// <summary>
        /// 修改
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public AllotResponse UpdateAllot(AllotRequest request)
        {
            var allot = _allotRepository.GetEntity(t => t.ID == request.ID);
            if (allot == null)
                throw new PerformanceException("当前绩效记录不存在");

            var repAllot = _allotRepository.GetEntities(t => t.ID != request.ID && t.HospitalId == request.HospitalId
                && t.Year == request.Year && t.Month == request.Month);
            if (repAllot != null && repAllot.Count() > 0)
                throw new PerformanceException("当前绩效记录与其他记录冲突");

            allot.HospitalId = request.HospitalId.Value;
            allot.Year = request.Year;
            allot.Month = request.Month;
            if (!_allotRepository.Update(allot))
                throw new PerformanceException("保存失败");

            return Mapper.Map<AllotResponse>(allot);
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="iD"></param>
        /// <returns></returns>
        public bool DeleteAllot(int iD)
        {
            var allot = _allotRepository.GetEntity(t => t.ID == iD);
            if (allot == null)
                throw new PerformanceException("当前绩效记录不存在");
            var result = _allotRepository.Remove(allot);
            if (!string.IsNullOrEmpty(allot.Path))
            {
                var dpath = Path.Combine(_evn.ContentRootPath, "Files", $"{allot.HospitalId}", $"{allot.Year}{allot.Month.ToString().PadLeft(2, '0')}");
                var name = FileHelper.GetFileName(allot.Path);
                var path = Path.Combine(dpath, name);
                if (allot.Path != path)
                {
                    try
                    {
                        FileHelper.Move(allot.Path, path);
                        allot.Path = path;
                        _allotRepository.Remove(allot);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError("移动文件失败;{0}", ex);
                    }
                }

                FileHelper.CreateDirectory(dpath);
            }

            return result;
        }

        /// <summary>
        /// 查询
        /// </summary>
        /// <param name="allotId"></param>
        /// <returns></returns>
        public per_allot GetAllot(int allotId)
        {
            return _allotRepository.GetEntity(t => t.ID == allotId);
        }

        /// <summary>
        /// 修改
        /// </summary>
        /// <param name="allot"></param>
        /// <returns></returns>
        public bool Update(per_allot allot)
        {
            return _allotRepository.Update(allot);
        }
        #endregion

        public void UpdateAllotStates(int allotId, int states, string remark)
        {
            _allotRepository.UpdateAllotStates(allotId, states, remark);
        }

        /// <summary>
        /// 生成绩效
        /// </summary>
        /// <param name="allot"></param>
        /// <param name="user"></param>
        public void Generate(per_allot allot, string mail)
        {
            DateTime time = DateTime.Now;
            try
            {
                logManageService.WriteMsg("绩效开始执行", $"正在生成{allot.Year}-{allot.Month.ToString().PadLeft(2, '0')}月份绩效!", 1, allot.ID, "ReceiveMessage", true);
                configService.Clear(allot.ID);

                // 导出数据
                var excel = importDataService.ReadDataAndSave(allot);

                UpdateAllotStates(allot.ID, (int)AllotStates.InCheckData, EnumHelper.GetDescription(AllotStates.InCheckData));
                if (!checkDataService.Check(excel, allot))
                {
                    UpdateAllotStates(allot.ID, (int)AllotStates.CheckFail, EnumHelper.GetDescription(AllotStates.CheckFail));
                    SendEmail(allot, mail, 3, time);
                    logManageService.WriteMsg("绩效数据校验失败", "详情可至“更多 -- 查看日志”查看", 3, allot.ID, "ReceiveMessage", true);
                    return;
                }

                UpdateAllotStates(allot.ID, (int)AllotStates.InGenerate, EnumHelper.GetDescription(AllotStates.InGenerate));

                //保底绩效计算需分两次进行；
                //第一次计算包含（保底绩效临床医生人均绩效、保底绩效医技医生人均绩效、保底绩效护士人均绩效）得出科室业绩绩效；
                //根据当前业绩绩效核算各人群人均绩效，取工勤人均作为 保底工勤人均绩效

                //第二次计算包含（保底绩效临床医生人均绩效、保底绩效医技医生人均绩效、保底绩效护士人均绩效、保底工勤人均绩效）得出科室业绩绩效；
                //根据当前业绩绩效核算出最终绩效结果


                //科室经济核算汇总、工作量汇总
                logManageService.WriteMsg("科室业绩汇总", "正在生成科室收入支出汇总、工作量汇总", 1, allot.ID, "ReceiveMessage", true);
                var (list, mergeSheets) = processComputService.MergeCompute(excel, allot.ID);
                processComputService.Save(list, allot.ID);

                //计算保底绩效参考标准（保底绩效临床医生人均绩效、保底绩效医技医生人均绩效、保底绩效护士人均绩效） 
                logManageService.WriteMsg("计算保底绩效参考标准值", "正在保底绩效临床医生人均绩效、保底绩效医技医生人均绩效、保底绩效护士人均绩效", 1, allot.ID, "ReceiveMessage", true);
                var minimumBaiscnorm = processComputService.ComputeMinimum(excel, mergeSheets, allot.ID);

                //计算科室业绩（含保底绩效临床医生人均绩效、保底绩效医技医生人均绩效、保底绩效护士人均绩效）保底绩效
                logManageService.WriteMsg("计算保底绩效参考标准值", "计算科室业绩", 1, allot.ID, "ReceiveMessage", true);
                var sheet = processComputService.Compute(excel, mergeSheets, minimumBaiscnorm);

                //计算科室自定义保底绩效范围
                logManageService.WriteMsg("计算保底绩效参考标准值", "正在计算科室自定义保底绩效范围", 1, allot.ID, "ReceiveMessage", true);
                processComputService.ComputeCustomMinimum(excel, mergeSheets, minimumBaiscnorm, allot.ID);

                //计算各人群人均保底绩效
                logManageService.WriteMsg("计算保底绩效参考标准值", "正在生成保底绩效行政工勤人均绩效", 1, allot.ID, "ReceiveMessage", true);
                var minimumBaiscnorm2 = resultComputeService.Compute(allot, excel, sheet, true);
                if (minimumBaiscnorm2 != null && minimumBaiscnorm2.Any(t => t.PositionName == EnumHelper.GetDescription(PerforType.行政工勤)))
                {
                    var minimum = minimumBaiscnorm2.First(t => t.PositionName == EnumHelper.GetDescription(PerforType.行政工勤));
                    minimum.PositionName = EnumHelper.GetDescription(MinimumType.保底工勤);
                    minimumBaiscnorm.Add(minimum);
                }

                //保存 保底绩效参考标准
                logManageService.WriteMsg("计算保底绩效参考标准值", "保存保底绩效参考标准", 1, allot.ID, "ReceiveMessage", true);
                perforResbaiscnormRepository.AddRange(minimumBaiscnorm.ToArray());

                //重新计算科室业绩（含所有提供保底金额）
                logManageService.WriteMsg("正在生成绩效", "重新计算科室业绩，包含所有保底金额计算科室业绩", 1, allot.ID, "ReceiveMessage", true);
                var sheetLast = processComputService.Compute(excel, mergeSheets, minimumBaiscnorm);

                //保存计算过程数据
                logManageService.WriteMsg("正在生成绩效", "保存科室业绩结果及计算过程中产生的数据", 1, allot.ID, "ReceiveMessage", true);
                processComputService.Save(sheetLast, allot.ID);

                // 计算最总数据
                logManageService.WriteMsg("正在生成绩效", "计算最终绩效数据", 1, allot.ID, "ReceiveMessage", true);
                var baiscnormList = resultComputeService.Compute(allot, excel, sheetLast);
                logManageService.WriteMsg("正在生成绩效", "计算最终特殊科室绩效数据", 1, allot.ID, "ReceiveMessage", true);
                resultComputeService.SpecialUnitCompute(excel, allot, baiscnormList);
                logManageService.WriteMsg("正在生成绩效", "保存最终特殊科室绩效数据", 1, allot.ID, "ReceiveMessage", true);

                //保存 绩效人均参考标准
                logManageService.WriteMsg("正在生成绩效", "保存绩效人均参考标准", 1, allot.ID, "ReceiveMessage", true);
                perforResbaiscnormRepository.AddRange(baiscnormList.ToArray());

                UpdateAllotStates(allot.ID, (int)AllotStates.GenerateSucceed, EnumHelper.GetDescription(AllotStates.GenerateSucceed));

                logManageService.WriteMsg("正在生成报表数据", "正在生成报表数据", 1, allot.ID, "ReceiveMessage", true);
                var res = reportService.ImportData(allot.ID);
                logManageService.WriteMsg("正在生成报表数据", $"报表数据生成完成；受影响：{res}行", 1, allot.ID, "ReceiveMessage", true);

                //发送邮件
                logManageService.WriteMsg("正在发送邮件", "正在发送邮件", 1, allot.ID, "ReceiveMessage", true);
                SendEmail(allot, mail, 1, time);
                //logdbug.Add(allot.ID, "绩效开始执行", "绩效生成成功");
                logManageService.WriteMsg("绩效生成结束", "绩效生成成功", 5, allot.ID, "ReceiveMessage", true);
            }
            catch (Exception ex)
            {
                logManageService.WriteMsg("绩效生成失败", "程序异常，请重新尝试。", 4, allot.ID, "ReceiveMessage");
                logdbug.Add(allot.ID, "绩效开始执行", ex.ToString(), 4);
                UpdateAllotStates(allot.ID, (int)AllotStates.GenerateFail, EnumHelper.GetDescription(AllotStates.GenerateFail));
                SendEmail(allot, mail, 2, time);
                //throw ex;
            }
        }

        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="allot"></param>
        /// <param name="mail"></param>
        /// <param name="type"> 1 成功 2 异常 3 验证失败</param>
        private void SendEmail(per_allot allot, string mail, int type, DateTime time)
        {
            var hospital = perforHospitalRepository.GetEntity(t => t.ID == allot.HospitalId);

            var message = new EmailMessage();
            message.To = new List<string> { mail };
            message.DisplayName = "溯直健康";
            message.Subject = $"绩效生成任务结束提醒！";
            if (type == 1)
            {
                message.Body = $"<p>尊敬的用户！</p><p>&nbsp;&nbsp;&nbsp;&nbsp;数值健康提醒您，您在{time.ToString("yyyy-MM-dd hh:mm:ss")}发布" +
                    $"{hospital.HosName}{allot.Year}年{allot.Month}月的绩效生成任务<strong><span style=\"color:#E53333;\">“执行成功”</span></strong>，" +
                    $"您可以登录<a href=\"http://jixiao.suvalue.com\" target=\"_blank\">http://jixiao.suvalue.com</a>查看绩效结果。</p>";
            }
            else if (type == 2)
            {
                message.Body = $"<p>尊敬的用户！</p><p>&nbsp;&nbsp;&nbsp;&nbsp;数值健康提醒您，您在{time.ToString("yyyy-MM-dd hh:mm:ss")}发布" +
                    $"{hospital.HosName}{allot.Year}年{allot.Month}月的绩效生成任务<strong><span style=\"color:#E53333;\">“执行失败”</span></strong>，" +
                    $"执行过程中出现异常情况，我们将尽快解决问题。给您带来的不便我们深感歉意！</p>";
            }
            else if (type == 3)
            {
                message.Body = $"<p>尊敬的用户！</p><p>&nbsp;&nbsp;&nbsp;&nbsp;数值健康提醒您，您在{time.ToString("yyyy-MM-dd hh:mm:ss")}发布" +
                    $"{hospital.HosName}{allot.Year}年{allot.Month}月的绩效生成任务<strong><span style=\"color:#E53333;\">“校验失败”</span></strong>，" +
                    $"您可以登录<a href=\"http://jixiao.suvalue.com\" target=\"_blank\">http://jixiao.suvalue.com</a>查看详细清空。</p>";
            }

            //emailService.SendAsync(message);
        }

        /// <summary>
        /// 归档绩效记录
        /// </summary>
        /// <param name="allot"></param>
        public void Pigeonhole(per_allot allot)
        {
            allot.States = 8;
            allot.Remark = "归档";
            _allotRepository.Update(allot);
            //if (_allotRepository.Update(allot))
            //{
            //    var again = _againallotRepository.GetEntities(t => t.AllotID == allot.ID);
            //    if (again != null && again.Count > 0)
            //    {
            //        foreach (var item in again)
            //        {
            //            item.Remark = $"原状态：{item.States}，归档更改状态";
            //            item.States = 5;
            //            _againallotRepository.Update(item);
            //        }
            //    }
            //}
        }

        /// <summary>
        /// 检索数据是否合格
        /// </summary>
        /// <param name="allot"></param>
        public List<AgainAllotResponse> GetAgainAllotNotSucceed(per_allot allot)
        {
            var again = _againallotRepository.GetEntities(t => t.AllotID == allot.ID && (!t.States.HasValue || t.States.Value != 3));
            List<AgainAllotResponse> result = Mapper.Map<List<AgainAllotResponse>>(again);
            if (result != null && result.Count > 0)
            {
                result.ForEach(t => { t.Year = allot.Year; t.Month = allot.Month; });
            }
            return result;
        }

        /// <summary>
        /// 绩效校验结果
        /// </summary>
        /// <param name="allot"></param>
        /// <returns></returns>
        public List<log_check> AllotCheckResult(per_allot allot)
        {
            var list = perforLogcheckRepository.GetEntities(t => t.AllotID == allot.ID);
            if (list != null)
                list = list.OrderBy(t => t.Titile).ThenBy(t => t.ID).ToList();
            return list;
        }

        /// <summary>
        /// 绩效历史日志
        /// </summary>
        /// <param name="allot"></param>
        /// <returns></returns>
        public List<log_dbug> AllotLog(per_allot allot)
        {
            var list = logdbug.GetEntities(t => t.AllotID == allot.ID);
            if (list != null)
                list = list.OrderBy(t => t.CreateTime).ToList();
            return list;
        }
    }
}
