using AutoMapper;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using Performance.DtoModels;
using Performance.DtoModels.AppSettings;
using Performance.EntityModels;
using Performance.Infrastructure;
using Performance.Repository;
using Performance.Services.AllotCompute;
using Performance.Services.ExtractExcelService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;

namespace Performance.Services
{
    public class AllotService : IAutoInjection
    {
        private BaiscNormService baiscNormService;
        private ImportDataService importDataService;
        private ProcessComputService processComputService;
        private ResultComputeService resultComputeService;
        private PerforLogdbugRepository logdbug;
        private ConfigService configService;
        private IWebHostEnvironment _evn;
        private ILogger<AllotService> _logger;
        private readonly IMapper _mapper;
        private readonly DapperService _service;
        private readonly PerforUserRepository _userRepository;
        private PerforPerallotRepository _allotRepository;
        private IEmailService emailService;
        private readonly IOptions<Application> options;
        private readonly IOptions<AppConnection> _appConnection;
        private readonly ComputeDirector _computeDirector;
        private readonly PerforRescomputeRepository _perforRescomputeRepository;
        private readonly PerforImemployeeRepository _perforImEmployeeRepository;
        private PerforPeragainallotRepository _againallotRepository;
        private PerforLogcheckRepository perforLogcheckRepository;
        private readonly PerforHospitalRepository perforHospitalRepository;
        private readonly PerforResbaiscnormRepository perforResbaiscnormRepository;
        private PerforHospitalconfigRepository perforHospitalconfigRepository;
        private readonly RoleService roleService;
        private readonly UserService userService;
        private PerforCofdirectorRepository perforCofdirectorRepository;
        private readonly PerforReportRepository _reportRepository;
        private readonly PerforPeremployeeRepository _perforPeremployeeRepository;
        private readonly PerforPerbatchRepository _batchRepository;
        private readonly LogManageService logManageService;

        private readonly ReportService reportService;
        private readonly QueryDataService queryDataService;

        public AllotService(
            IMapper mapper,
            DapperService service,
            PerforUserRepository userRepository,
            PerforPerallotRepository allotRepository,
            BaiscNormService baiscNormService,
            ImportDataService importDataService,
            ProcessComputService processComputService,
            ResultComputeService resultComputeService,
            ConfigService configService,
            PerforLogdbugRepository logdbug,
            IWebHostEnvironment evn, ILogger<AllotService> logger,
            IEmailService emailService,
            IOptions<Application> options,
            IOptions<AppConnection> appConnection,
            ComputeDirector computeDirector,
            PerforRescomputeRepository perforRescomputeRepository,
            PerforImemployeeRepository perforImEmployeeRepository,
            PerforPeragainallotRepository againallotRepository,
            PerforLogcheckRepository perforLogcheckRepository,
            PerforHospitalRepository perforHospitalRepository,
            PerforResbaiscnormRepository perforResbaiscnormRepository,
            PerforHospitalconfigRepository perforHospitalconfigRepository,
            RoleService roleService,
            UserService userService,
            LogManageService logManageService,
            ReportService reportService,
            PerforCofdirectorRepository perforCofdirectorRepository,
            PerforReportRepository reportRepository,
            PerforPeremployeeRepository perforPeremployeeRepository,
            PerforPerbatchRepository batchRepository,
            QueryDataService queryDataService)
        {
            _mapper = mapper;
            _service = service;
            _userRepository = userRepository;
            _allotRepository = allotRepository;
            _againallotRepository = againallotRepository;
            _logger = logger;
            _evn = evn;
            this.baiscNormService = baiscNormService;
            this.importDataService = importDataService;
            this.processComputService = processComputService;
            this.resultComputeService = resultComputeService;
            this.emailService = emailService;
            this.options = options;
            _appConnection = appConnection;
            _computeDirector = computeDirector;
            _perforRescomputeRepository = perforRescomputeRepository;
            _perforImEmployeeRepository = perforImEmployeeRepository;
            this.configService = configService;
            this.logdbug = logdbug;
            this.perforLogcheckRepository = perforLogcheckRepository;
            this.perforHospitalRepository = perforHospitalRepository;
            this.perforResbaiscnormRepository = perforResbaiscnormRepository;
            this.perforHospitalconfigRepository = perforHospitalconfigRepository;
            this.roleService = roleService;
            this.userService = userService;
            //this.hubContext = hubContext;
            this.logManageService = logManageService;
            this.reportService = reportService;
            this.perforCofdirectorRepository = perforCofdirectorRepository;
            _reportRepository = reportRepository;
            _perforPeremployeeRepository = perforPeremployeeRepository;
            _batchRepository = batchRepository;
            this.queryDataService = queryDataService;
        }

        #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 isconfig = perforHospitalconfigRepository.GetEntity(t => t.HospitalId == hospitalId) == null ? false : true;
            var result = _mapper.Map<List<AllotResponse>>(allotList);
            result?.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("\\", "/");

                t.HasConfig = isconfig ? 3 : 0;
            });
            if (result != null && result.Any())
            {
                result = result.OrderByDescending(t => t.Year).ThenByDescending(t => t.Month).ToList();
            }
            return result;
        }

        /// <summary>
        /// 生成成功或归档绩效记录
        /// </summary>
        /// <param name="hospitalId"></param>
        /// <returns></returns>
        public List<AllotResponse> GetSuccAllotList(int? hospitalId)
        {
            if (!hospitalId.HasValue || hospitalId.Value <= 0)
                throw new PerformanceException("hospitalId无效");
            var allotList = _allotRepository.GetEntities(t => t.HospitalId == hospitalId
                && new List<int> { (int)AllotStates.归档, (int)AllotStates.绩效下发 }.Contains(t.States));
            allotList = allotList == null ? allotList : allotList.OrderByDescending(t => t.ID).ToList();
            var isconfig = perforHospitalconfigRepository.GetEntity(t => t.HospitalId == hospitalId) == null ? false : true;
            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("\\", "/");

                t.HasConfig = isconfig ? 3 : 0;
            });
            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.数据未上传;
            allot.Remark = EnumHelper.GetDescription(AllotStates.数据未上传);
            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);

                        configService.ClearAllotData(allot.ID);
                    }
                    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 基础功能

        /// <summary>
        /// 修改自定义提取结果地址
        /// </summary>
        /// <param name="allotId"></param>
        /// <param name="path"></param>
        public void UpdateAllotCustomExtractPath(int allotId, string path)
        {
            var allot = _allotRepository.GetEntity(w => w.ID == allotId);
            allot.CustomExtractPath = path;
            _allotRepository.Update(allot);
        }

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

        public per_allot UpdateAllotShowFormula(int allotId)
        {
            var allot = _allotRepository.GetEntity(t => t.ID == allotId);
            if (allot == null)
                return null;
            allot.ShowFormula = allot.ShowFormula == 0 ? 1 : 0;
            _allotRepository.Update(allot);
            return allot;
        }

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

                UpdateAllotStates(allot.ID, (int)AllotStates.正在校验数据, EnumHelper.GetDescription(AllotStates.正在校验数据));
                var excel = new PerExcel();
                int generate = allot.Generate;
                if (new int[] { (int)AllotGenerate.OriginalDataEdited, (int)AllotGenerate.PersonnelOffice }.Contains(allot.Generate))
                {
                    logManageService.WriteMsg("绩效开始执行", $"数据来源：生成成功后被修改的原数据。", 1, allot.ID, "ReceiveMessage", true);
                    generate = (int)AllotGenerate.Success;
                    configService.ClearResData(allot.ID);
                    excel = queryDataService.QueryDataAndHeader(allot);
                }
                else
                {
                    logManageService.WriteMsg("绩效开始执行", $"数据来源：用户上传的Excel。", 1, allot.ID, "ReceiveMessage", true);

                    configService.Clear(allot.ID);
                    // 关闭筛选功能
                    ExtractHelper.CloseAutoFilter(allot.Path);
                    // 导出数据
                    excel = importDataService.ReadDataAndSave(allot);

                    //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;
                    //}
                }
                _service.UpdateAllotStates(allot.ID, (int)AllotStates.正在生成绩效, EnumHelper.GetDescription(AllotStates.正在生成绩效));

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

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

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

                #region 注释

                ////计算保底绩效参考标准（保底绩效临床医生人均绩效、保底绩效医技医生人均绩效、保底绩效护士人均绩效）
                //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, true);

                ////计算科室自定义保底绩效范围
                //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 accountExtras = processComputService.GetAccountExtra(excel, SheetType.AccountExtra, true);
                //// 科室药占比考核
                //logManageService.WriteMsg("正在生成绩效", "科室药占比考核", 1, allot.ID, "ReceiveMessage", true);
                //var drugExtras = processComputService.GetAccountExtra(excel, SheetType.AccountDrugAssess, true);
                //// 科室材料占比考核
                //logManageService.WriteMsg("正在生成绩效", "科室材料占比考核", 1, allot.ID, "ReceiveMessage", true);
                //var materialsExtras = processComputService.GetAccountExtra(excel, SheetType.AccountMaterialsAssess, true);
                //// 科室考核得分率
                //logManageService.WriteMsg("正在生成绩效", "科室考核得分率", 1, allot.ID, "ReceiveMessage", true);
                //var accountScoreAverages = processComputService.GetAccountScoreAverage(excel, SheetType.AccountScoreAverage, true);

                ////科室药占比考核
                //logManageService.WriteMsg("正在生成绩效", "科室奖罚汇总", 1, allot.ID, "ReceiveMessage", true);
                //var accountExtras = processComputService.GetAccountExtra(excel);

                ////科室材料考核
                //logManageService.WriteMsg("正在生成绩效", "科室奖罚汇总", 1, allot.ID, "ReceiveMessage", true);
                //var accountExtras = processComputService.GetAccountExtra(excel);

                #endregion 注释

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

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

                ////业务中层行政中高层医院奖罚
                //logManageService.WriteMsg("正在生成绩效", "业务中层行政中高层医院奖罚", 1, allot.ID, "ReceiveMessage", true);
                //var employeeExtra = processComputService.GetEmployeeExtra(excel);

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

                // 计算行政科室绩效
                processComputService.ComputeOffice(allot, excel);

                // 计算特殊科室
                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());

                //// 保存预留绩效
                //logManageService.WriteMsg("正在生成绩效", "保存预留绩效金额", 1, allot.ID, "ReceiveMessage", true);
                //resultComputeService.SaveReserved(allot, allot.HospitalId);

                logManageService.WriteMsg("正在生成绩效", "验证科室核算单元&工号", 1, allot.ID, "ReceiveMessage", true);
                // 验证科室核算单元、工号
                _allotRepository.AccoungtingVerify(allot.ID);

                logManageService.WriteMsg("正在生成绩效", "待下发科室准备", 1, allot.ID, "ReceiveMessage", true);
                // 科室创建但不下发
                resultComputeService.GenerateSecondAllot(allot);
                _service.SecondUseTempRestore();
                _service.RestoreSecondAllot();
                _service.ClearAllot();
                logManageService.WriteMsg("正在生成绩效", "绩效数据存储", 1, allot.ID, "ReceiveMessage", true);
                _service.FreezeAllotSync(allot.ID);

                logManageService.WriteMsg("绩效生成结束", "绩效生成成功", 1, allot.ID, "ReceiveMessage", true);
                _service.UpdateAllotStates(allot.ID, (int)AllotStates.绩效结果解析成功, EnumHelper.GetDescription(AllotStates.绩效结果解析成功), generate);

                logManageService.WriteMsg("额外操作", "补充费用占比类型", 1, allot.ID, "ReceiveMessage", true);
                perforCofdirectorRepository.SupplementaryData(allot.ID);
                logManageService.WriteMsg("额外操作", "补全支出费用类别", 1, allot.ID, "ReceiveMessage", true);
                //补全支出费用类别
                configService.SaveDrugtypeDisburse(allot.ID);
                //logManageService.WriteMsg("正在生成报表数据", "正在生成报表数据", 1, allot.ID, "ReceiveMessage", true);
                //var res = reportService.ImportData(allot);
                //var flag = reportService.UpdateData(allot);
                //logManageService.WriteMsg("正在生成报表数据", $"报表数据生成完成；受影响：{res}行", 1, allot.ID, "ReceiveMessage", true);
                //reportService.ExecProc("call proc_report_performance(@hospitalid, @year, @month);", new { allot.HospitalId, allot.Year, allot.Month });

                ////发送邮件
                //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("绩效生成失败", ex.Message, 4, allot.ID, "ReceiveMessage");
                _service.UpdateAllotStates(allot.ID, (int)AllotStates.绩效解析失败, EnumHelper.GetDescription(AllotStates.绩效解析失败));
                //SendEmail(allot, mail, 2, time);
                //throw ex;
            }
        }

        public EmpDeptCheckBase GetCheckBase(int allotid)
        {
            var empDeptCheckBase = new EmpDeptCheckBase()
            {
                CheckEmp = new CheckBase()
                {
                    Body = _service.QueryView("view_check_base_emp", allotid),
                    Columns = _service.QueryTableStructure("view_check_base_emp")
                },
                CheckDept = new CheckBase()
                {
                    Body = _service.QueryView("view_check_base_dept", allotid),
                    Columns = _service.QueryTableStructure("view_check_base_dept")
                },
            };

            return empDeptCheckBase;
        }

        /// <summary>
        /// 绩效生成报表
        /// </summary>
        /// <param name="allot"></param>
        public void GenerateReport(per_allot allot)
        {
            try
            {
                reportService.ExecProc("call proc_report_performance(@hospitalid, @year, @month);", new { allot.HospitalId, allot.Year, allot.Month });
            }
            catch (Exception ex)
            {
                _logger.LogError($"{allot.Year}年{allot.Month}月绩效报表生成失败。 allotid: {allot.ID}。" + ex.Message);
            }
        }

        /// <summary>
        /// 绩效生成报表
        /// </summary>
        /// <param name="allot"></param>
        public void GenerateReportStatistics(int allotId)
        {
            try
            {
                reportService.ExecProc("call proc_report_statistics(@allotId);", new { allotId });
            }
            catch (Exception ex)
            {
                _logger.LogError($"每月汇报表生成失败。 allotid: {allotId}。" + ex.Message);
            }
        }
        /// <summary>
        /// 验证科室核算单元、工号
        /// </summary>
        /// <param name="allot"></param>
        public void AccoungtingVerify(int allotId)
        {
            _allotRepository.AccoungtingVerify(allotId);
        }

        /// <summary>
        /// 重新计算院领导绩效
        /// </summary>
        /// <param name="allotId"></param>
        /// <param name="money"></param>
        public void Recalculation(int allotId, decimal money)
        {
            var allot = _allotRepository.GetEntity(w => w.ID == allotId);

            var empolyeeList = _perforImEmployeeRepository.GetEntities(t => t.AllotID == allotId && t.AccountType == AccountUnitType.行政高层.ToString());

            if (empolyeeList == null) return;

            var computeEmployees = _mapper.Map<List<ComputeEmployee>>(empolyeeList);
            computeEmployees.ForEach(w => w.FitPeopleValue = money);

            List<res_baiscnorm> baiscnormList = new List<res_baiscnorm>();
            var computResult = _computeDirector.Compute(computeEmployees, allot, baiscnormList);

            if (computResult == null) return;

            baiscnormList = baiscNormService.ComputeOtherAvg(baiscnormList, computResult, empolyeeList);
            baiscnormList.ForEach(t => t.AllotID = allot.ID);

            var historyRescompute = _perforRescomputeRepository.GetEntities(w => w.AllotID == allotId && w.AccountType == AccountUnitType.行政高层.ToString());
            if (historyRescompute != null && historyRescompute.Any())
                _perforRescomputeRepository.RemoveRange(historyRescompute.ToArray());

            var computes = _mapper.Map<List<res_compute>>(computResult);
            computes.ForEach(t => t.AllotID = allot.ID);
            _perforRescomputeRepository.AddRange(computes.ToArray());

            var names = baiscnormList.Select(b => b.PositionName).ToList();
            var historyResbaiscnorm = perforResbaiscnormRepository.GetEntities(w => w.AllotID == allotId && names.Contains(w.PositionName));
            if (historyResbaiscnorm != null && historyResbaiscnorm.Any())
                perforResbaiscnormRepository.RemoveRange(historyResbaiscnorm.ToArray());
            perforResbaiscnormRepository.AddRange(_mapper.Map<res_baiscnorm[]>(baiscnormList));
        }

        /// <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 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, int type)
        {
            var list = logdbug.GetEntities(t => t.AllotID == allot.ID && t.Type == type);
            if (list != null)
                list = list.OrderBy(t => t.CreateTime).ToList();
            return list;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="userid"></param>
        /// <returns></returns>
        public List<EmployeeReservedDto> GetReserved(ReservedRequest request, int userid)
        {
            var userInfo = _userRepository.GetUser(userid);
            if (userInfo?.User == null) throw new NotImplementedException("当前用户不存在");
            if (userInfo?.URole == null) throw new NotImplementedException("当前用户暂未分配角色");

            var role = roleService.GetUserRole(userid)?.FirstOrDefault()?.Type;
            if (!role.HasValue)
                throw new PerformanceException("用户信息错误");

            var reserveds = _reportRepository.GetEmployeeReserved(request);

            if (reserveds != null && reserveds.Any() && userInfo.IsSecondAdmin)
            {
                var unitTpes = UnitTypeUtil.GetMaps(userInfo?.URole.Type);
                reserveds = reserveds.Where(w => UnitTypeUtil.Is(w.UnitType, unitTpes) && w.AccountingUnit == userInfo.User.Department)?.ToList();
            }
            return reserveds;
        }

        /// <summary>
        /// 预留金额下载
        /// </summary>
        /// <param name="rows"></param>
        /// <param name="name"></param>
        /// <param name="allotId"></param>
        /// <param name="headList"></param>
        /// <returns></returns>
        public string ExcelDownload(List<Dictionary<string, object>> rows, string name, List<ExcelDownloadHeads> excelDownloadHeads, params string[] ignoreColumns)
        {
            var dpath = Path.Combine(_evn.ContentRootPath, "Files", "PerformanceRelease");
            FileHelper.CreateDirectory(dpath);
            string filepath = Path.Combine(dpath, $"{name}-{DateTime.Now:yyyy年MM月dd日}");
            FileHelper.DeleteFile(filepath);

            string[] notSum = new string[] { "year", "month", "jobnumber", "source", "unittype", "accountingunit", "newunittype", "newaccountingunit", "employeename" };
            using (FileStream fs = new FileStream(filepath, FileMode.OpenOrCreate))
            using (ExcelPackage package = new ExcelPackage(fs))
            {
                var worksheet = package.Workbook.Worksheets.Add(name);

                if (rows != null && rows.Count() > 0)
                {
                    worksheet.SetValue(1, 1, name);
                    var headList = rows.FirstOrDefault().Where(w => !ignoreColumns.Contains(w.Key.ToLower())).ToList();
                    for (int col = 0; col < headList.Count; col++)
                    {
                        string headKey = headList[col].Key;
                        string head;

                        head = headKey.Contains("ReseFee") ? "预留金额" : headKey.Contains("GiveFee") ? "实发金额" : headKey;

                        foreach (var item in excelDownloadHeads)
                        {
                            if (headKey == item.Name)
                            {
                                head = item.Alias;
                                break;
                            }
                        };
                        worksheet.SetValue(3, col + 1, head);
                    }
                    for (int col = 0; col < headList.Count; col++)
                    {
                        for (int row = 0; row < rows.Count(); row++)
                        {
                            var temp = rows.ElementAt(row);
                            var value = temp[headList[col].Key] ?? "";
                            worksheet.Cells[row + 4, col + 1].Value = value;
                        }
                        if (col == 0)
                            worksheet.SetValue(rows.Count() + 4, col + 1, "合计");
                        else if (!notSum.Contains(headList[col].Key.ToLower()))
                            worksheet.Cells[rows.Count() + 4, col + 1].Formula = string.Format("SUM({0})", new ExcelAddress(4, col + 1, rows.Count() + 3, col + 1).Address);
                    }

                    #region 样式设置
                    int num = name.Contains("科室") ? 6 : 5;
                    for (int i = 1; i <= num; i++)
                    {
                        worksheet.SetValue(2, i, worksheet.GetValue(3, i));
                        worksheet.Cells[2, i, 3, i].Merge = true;
                    }
                    int month = 1;
                    for (int i = num + 1; i <= headList.Count; i += 2)
                    {
                        worksheet.SetValue(2, i, month + "月");
                        if (month == 13)
                            worksheet.SetValue(2, i, "合计");

                        worksheet.Cells[2, i, 2, i + 1].Merge = true;
                        month++;
                    }

                    for (int row = worksheet.Dimension.Start.Row; row <= worksheet.Dimension.End.Row; row++)
                    {
                        worksheet.Row(row).Height = 20;
                        for (int col = worksheet.Dimension.Start.Column; col <= worksheet.Dimension.End.Column; col++)
                        {
                            if (col <= headList.Count && !notSum.Contains(headList[col - 1].Key.ToLower()))
                            {
                                worksheet.Cells[row, col].Style.Numberformat.Format = "#,##0.00";
                            }

                            worksheet.Cells[row, col].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
                            worksheet.Cells[row, col].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
                            worksheet.Cells[row, col].Style.Border.BorderAround(ExcelBorderStyle.Thin);
                        }
                    }


                    worksheet.Cells[1, 1, 1, headList.Count].Merge = true;
                    worksheet.Cells[1, 1, 1, headList.Count].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
                    worksheet.Cells[1, 1, 1, headList.Count].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
                    worksheet.Cells[1, 1, 1, headList.Count].Style.Font.Bold = true;
                    worksheet.Cells[1, 1, 1, headList.Count].Style.Font.Size = 16;
                    worksheet.Row(1).Height = 24;
                    worksheet.Cells[2, 1, 2, headList.Count].Style.Font.Bold = true;
                    worksheet.View.FreezePanes(4, 1);
                    worksheet.Cells.AutoFitColumns();
                    for (int col = worksheet.Dimension.Start.Column; col <= worksheet.Dimension.End.Column; col++)
                    {
                        worksheet.Column(col).Width = worksheet.Column(col).Width > 20 ? 20 : worksheet.Column(col).Width;
                    }
                    #endregion
                }
                package.Save();
            }
            return filepath;
        }

        /// <summary>
        /// 查询个人绩效
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public List<OwnerPerformanceDto> GetOwnerPerformance(int userid)
        {
            var user = userService.GetUser(userid);
            if (user == null)
                throw new PerformanceException("用户信息错误");

            var roleType = roleService.GetUserRole(userid)?.FirstOrDefault()?.Type;
            if (!roleType.HasValue)
                throw new PerformanceException("用户信息错误");

            if (roleType != (int)Role.绩效查询)
                throw new PerformanceException("当前用户暂不支持绩效查询");

            var hospitals = userService.GetUserHospital(userid);
            string jobNumber = user.Login;
            var owner = _reportRepository.GetOwnerPerformance(hospitals, jobNumber);

            if (owner == null || !owner.Any())
                return new List<OwnerPerformanceDto>();

            var employees = _perforPeremployeeRepository.GetEntities(w => w.PersonnelNumber == jobNumber);
            var allotIds = owner.Select(w => w.AllotId).Distinct().ToList();
            var batchs = _batchRepository.GetEntities(w => allotIds.Contains(w.AllotId));
            var allots = _allotRepository.GetEntities(w => allotIds.Contains(w.ID));
            // 发放时间 归档状态永远显示，如果没有发放时间则使用归档时间，绩效下发状态下显示标记发放时间
            Func<OwnerPerformanceDto, string> getIssueDate = (owner) =>
            {
                var batch = batchs.FirstOrDefault(p => p.AllotId == owner.AllotId
                    && p.UnitType == owner.UnitType && p.AccountingUnit == owner.AccountingUnit
                    && p.PersonnelNumber == owner.JobNumber);

                var allot = allots.FirstOrDefault(w => w.ID == owner.AllotId);
                if (batch?.BatchDate != null && batch.BatchDate.HasValue)
                    return batch.BatchDate.Value.ToString("yyyy年MM月dd日 HH:mm");
                else if (allot?.States == (int)AllotStates.归档)
                {
                    if (allot?.PigeonholeDate != null && allot.PigeonholeDate.HasValue)
                        return allot.PigeonholeDate.Value.ToString("yyyy年MM月dd日 HH:mm");
                    return $"{allot.Year}年{allot}月";
                }
                return "";
            };

            var res = owner
                .GroupBy(w => new { w.AllotId, w.Year, w.Month, w.JobNumber })
                .Select(w => new OwnerPerformanceDto
                {
                    AllotId = w.Key.AllotId,
                    Year = w.Key.Year,
                    Month = w.Key.Month,
                    JobNumber = w.Key.JobNumber,

                    PerforSumFee = Math.Round(w.Sum(p => p.PerforSumFee) ?? 0, 2, MidpointRounding.AwayFromZero),     // 业绩绩效
                    PerforManagementFee = Math.Round(w.Sum(p => p.PerforManagementFee) ?? 0, 2, MidpointRounding.AwayFromZero),// 管理绩效
                    NightWorkPerfor = Math.Round(w.Sum(p => p.NightWorkPerfor) ?? 0, 2, MidpointRounding.AwayFromZero),// 夜班绩效
                    AdjustLaterOtherFee = Math.Round(w.Sum(p => p.AdjustLaterOtherFee) ?? 0, 2, MidpointRounding.AwayFromZero), // 调节后其他绩效
                    OtherPerfor = Math.Round(w.Sum(p => p.OtherPerfor) ?? 0, 2, MidpointRounding.AwayFromZero), // 医院其他绩效
                    HideOtherPerfor = Math.Round(w.Sum(p => p.HideOtherPerfor) ?? 0, 2, MidpointRounding.AwayFromZero),// 不公示其他绩效

                    RealPerformance = Math.Round(w.Sum(p => p.RealPerformance) ?? 0, 2, MidpointRounding.AwayFromZero),// 中转使用： 业绩绩效+管理绩效+调节后其他绩效
                    Detail = w
                        .Where(p => p.Year == w.Key.Year && p.Month == w.Key.Month && p.JobNumber == w.Key.JobNumber)
                        .Select(detial =>
                        {
                            var dto = _mapper.Map<OwnerPerformanceDto>(detial);
                            dto.Source = string.IsNullOrEmpty(detial.SourceItem) ? detial.Source : $"{detial.Source}-{detial.SourceItem}";
                            dto.IssueDate = getIssueDate(dto);
                            // 应发绩效
                            dto.ShouldGiveFee = Math.Round((dto.RealPerformance ?? 0) + (dto.OtherPerfor ?? 0) + (dto.HideOtherPerfor ?? 0) + (dto.NightWorkPerfor ?? 0), 2, MidpointRounding.AwayFromZero);
                            dto.ReservedRatio = employees?.FirstOrDefault(emp => emp.AllotId == dto.AllotId && emp.PersonnelNumber == jobNumber)?.ReservedRatio ?? 0; // 预留比例
                            dto.ReservedRatioFee = Math.Round((dto.RealPerformance ?? 0) * (dto.ReservedRatio ?? 0), 2, MidpointRounding.AwayFromZero);  // 预留绩效
                            dto.RealGiveFee = Math.Round(dto.ShouldGiveFee - (dto.ReservedRatioFee ?? 0) ?? 0, 2, MidpointRounding.AwayFromZero); // 实发绩效
                            return dto;
                        })
                        .Where(w => !string.IsNullOrEmpty(w.IssueDate)),
                })
                .ToList();

            foreach (var item in res)
            {
                item.UnitType = employees?.FirstOrDefault(w => w.AllotId == item.AllotId && w.PersonnelNumber == jobNumber)?.UnitType ?? "";
                item.AccountingUnit = employees?.FirstOrDefault(w => w.AllotId == item.AllotId && w.PersonnelNumber == jobNumber)?.AccountingUnit ?? "";
                item.EmployeeName = employees?.FirstOrDefault(w => w.AllotId == item.AllotId && w.PersonnelNumber == jobNumber)?.DoctorName ?? "";
                // 全部是0的记录不显示
                item.Detail = item.Detail.Where(w => !(w.PerforSumFee == 0 && w.PerforManagementFee == 0 && w.ShouldGiveFee == 0 && w.OtherPerfor == 0 && w.HideOtherPerfor == 0 && w.RealGiveFee == 0));
                // 为了保证总额一致
                item.ShouldGiveFee = Math.Round(item.Detail?.Sum(w => w.ShouldGiveFee) ?? 0, 2, MidpointRounding.AwayFromZero); // 应发绩效
                item.ReservedRatio = employees?.FirstOrDefault(w => w.AllotId == item.AllotId && w.PersonnelNumber == jobNumber)?.ReservedRatio ?? 0; // 预留比例
                item.ReservedRatioFee = Math.Round(item.Detail?.Sum(w => w.ReservedRatioFee) ?? 0, 2, MidpointRounding.AwayFromZero);  // 预留绩效
                item.RealGiveFee = Math.Round(item.Detail?.Sum(w => w.RealGiveFee) ?? 0, 2, MidpointRounding.AwayFromZero); // 实发绩效
            }

            return res?.OrderByDescending(w => w.Year).ThenByDescending(w => w.Month).ToList();
        }

        /// <summary>
        /// 格式转换
        /// </summary>
        /// <param name="userid"></param>
        /// <param name="beginDate"></param>
        /// <param name="endDate"></param>
        /// <returns></returns>
        public List<OwnerMobilePerformanceDto> GetOwnerMobilePerformance(int userid, DateTime beginDate, DateTime endDate)
        {
            List<OwnerMobilePerformanceDto> dtos = new List<OwnerMobilePerformanceDto>();
            var datas = GetOwnerPerformance(userid);

            if (datas == null || datas.Count == 0)
                return dtos;

            var filterDatas = datas
                .Where(w => (new DateTime(w.Year, w.Month, 1)) >= beginDate && (new DateTime(w.Year, w.Month, 1)) < endDate);
            var groupDatas = filterDatas.GroupBy(w => w.JobNumber);

            foreach (var item in groupDatas)
            {
                var perforSumFee = new OwnerMobileItemDto
                {
                    Title = "业绩绩效",
                    Amount = item.Sum(w => w.PerforSumFee) ?? 0,
                    Details = item.SelectMany(w => w.Detail)
                        .Where(w => (w.PerforSumFee ?? 0) != 0 && !string.IsNullOrEmpty(w.IssueDate))
                        .Select(w => new OwnerMobileItemDetailDto
                        {
                            Title = w.AccountingUnit,
                            Amount = w.PerforSumFee ?? 0,
                            Date = w.IssueDate,
                        }).ToList()
                };
                var perforManagementFee = new OwnerMobileItemDto
                {
                    Title = "管理绩效",
                    Amount = item.Sum(w => w.PerforManagementFee) ?? 0,
                    Details = item.SelectMany(w => w.Detail)
                        .Where(w => (w.PerforManagementFee ?? 0) != 0 && !string.IsNullOrEmpty(w.IssueDate))
                        .Select(w => new OwnerMobileItemDetailDto
                        {
                            Title = w.AccountingUnit,
                            Amount = w.PerforManagementFee ?? 0,
                            Date = w.IssueDate,
                        }).ToList()
                };
                var nightWorkPerfor = new OwnerMobileItemDto
                {
                    Title = "夜班绩效",
                    Amount = item.Sum(w => w.NightWorkPerfor) ?? 0,
                    Details = item.SelectMany(w => w.Detail)
                        .Where(w => (w.NightWorkPerfor ?? 0) != 0 && !string.IsNullOrEmpty(w.IssueDate))
                        .Select(w => new OwnerMobileItemDetailDto
                        {
                            Title = w.AccountingUnit,
                            Amount = w.NightWorkPerfor ?? 0,
                            Date = w.IssueDate,
                        }).ToList()
                };
                var otherPerfor = new OwnerMobileItemDto
                {
                    Title = "医院其他绩效",
                    Amount = item.Sum(w => w.OtherPerfor) ?? 0,
                    Details = item.SelectMany(w => w.Detail)
                        .Where(w => (w.OtherPerfor ?? 0) != 0 && !string.IsNullOrEmpty(w.IssueDate))
                        .Select(w => new OwnerMobileItemDetailDto
                        {
                            Title = w.SourceItem,
                            Amount = w.OtherPerfor ?? 0,
                            Date = w.IssueDate,
                        }).ToList()
                };
                var hideOtherPerfor = new OwnerMobileItemDto
                {
                    Title = "不公示其他绩效",
                    Amount = item.Sum(w => w.HideOtherPerfor) ?? 0,
                    Details = item.SelectMany(w => w.Detail)
                        .Where(w => (w.HideOtherPerfor ?? 0) != 0 && !string.IsNullOrEmpty(w.IssueDate))
                        .Select(w => new OwnerMobileItemDetailDto
                        {
                            Title = w.SourceItem,
                            Amount = w.HideOtherPerfor ?? 0,
                            Date = w.IssueDate,
                        }).ToList()
                };
                var reservedRatioFee = new OwnerMobileItemDto
                {
                    Title = "预留绩效",
                    Amount = item.Sum(w => w.ReservedRatioFee) ?? 0,
                    Details = new List<OwnerMobileItemDetailDto>()
                };

                var dto = new OwnerMobilePerformanceDto { Total = item.Sum(w => w.RealGiveFee), Items = new List<OwnerMobileItemDto>() };
                if (perforSumFee.Amount != 0) dto.Items.Add(perforSumFee);
                if (perforManagementFee.Amount != 0) dto.Items.Add(perforManagementFee);
                if (nightWorkPerfor.Amount != 0) dto.Items.Add(nightWorkPerfor);
                if (otherPerfor.Amount != 0) dto.Items.Add(otherPerfor);
                if (hideOtherPerfor.Amount != 0) dto.Items.Add(hideOtherPerfor);
                if (reservedRatioFee.Amount != 0) dto.Items.Add(reservedRatioFee);

                dtos.Add(dto);
            }

            return dtos;
        }
    }
}
