using Microsoft.Extensions.Logging;
using NPOI.SS.UserModel;
using Performance.DtoModels;
using Performance.EntityModels;
using Performance.Infrastructure;
using Performance.Repository;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Performance.Services.ExtractExcelService
{
    public class ExtractService : IAutoInjection
    {
        private readonly ILogger logger;
        private readonly LogManageService logService;
        private readonly QueryService queryService;
        private readonly PersonService personService;
        private readonly PerSheetService perSheetService;
        private readonly PerforHospitalRepository hospitalRepository;
        private readonly PerforPerallotRepository perallotRepository;
        private readonly PerforcollectdataRepository collectdataRepository;
        private readonly PerforPeremployeeRepository peremployeeRepository;

        public ExtractService(
            ILogger<ExtractService> logger,
            LogManageService logService,
            QueryService queryService,
            PersonService personService,
            PerSheetService perSheetService,
            PerforHospitalRepository hospitalRepository,
            PerforPerallotRepository perallotRepository,
            PerforcollectdataRepository collectdataRepository,
            PerforPeremployeeRepository peremployeeRepository
            )
        {
            this.logger = logger;
            this.logService = logService;
            this.queryService = queryService;
            this.personService = personService;
            this.perSheetService = perSheetService;
            this.hospitalRepository = hospitalRepository;
            this.perallotRepository = perallotRepository;
            this.collectdataRepository = collectdataRepository;
            this.peremployeeRepository = peremployeeRepository;
        }

        /// <summary>
        /// 抽取绩效文件
        /// </summary>
        /// <param name="allotId">抽取绩效Id</param>
        /// <param name="hospitalId">医院Id</param>
        /// <param name="email">邮箱地址</param>
        /// <param name="groupName">即时日志分组名称</param>
        /// <param name="filePath">历史提交文件地址</param>
        public string Main(int allotId, int hospitalId, string email, string groupName = null, string filePath = null)
        {
            string extractFilePath = "";
            IWorkbook workbook = null;
            try
            {
                logService.ClearExtractLog(allotId);

                var hospital = hospitalRepository.GetEntity(t => t.ID == hospitalId);

                var allots = perallotRepository.GetEntities(t => t.HospitalId == hospitalId);
                if (allots == null || !allots.Any(t => t.ID == allotId)) throw new Exception("绩效不存在");

                var allot = allots.First(t => t.ID == allotId);
                var dict = new Dictionary<ExDataDict, object>();
                var data = queryService.Handler(hospitalId, allot, ref dict);

                var standData = StandDataFormat(hospitalId, data);

                var statesArray = new int[] { (int)AllotStates.GenerateSucceed, (int)AllotStates.Archive };
                //var lastAllot = allots.Where(t => statesArray.Contains(t.States))?.OrderByDescending(t => t.Year).ThenByDescending(t => t.Month).FirstOrDefault();

                var templateFilePath = ExtractHelper.GetExtractFile(hospitalId, ref extractFilePath, filePath);

                if (!FileHelper.IsExistFile(templateFilePath)) throw new PerformanceException("抽取文件创建失败");

                workbook = ExcelHelper.GetWorkbook(templateFilePath);
                if (workbook == null) throw new PerformanceException("文件读取失败");

                WriteDataToFile(workbook, allotId, dict, standData);
            }
            catch (Exception ex)
            {
                logger.LogError("提取数据中发生异常: " + ex.ToString());
            }
            finally
            {
                using (FileStream file = new FileStream(extractFilePath, FileMode.OpenOrCreate))
                {
                    workbook.Write(file);
                }
                workbook.Close();
            }
            return extractFilePath;
        }

        private void WriteDataToFile(IWorkbook workbook, int allotId, Dictionary<ExDataDict, object> exdict, List<ExtractTransDto> extractDto)
        {
            ExcelStyle style = new ExcelStyle(workbook);

            var models = exdict[ExDataDict.ExModule] as List<ex_module>;
            ExtractHelper.CreateNotExistSheet(models, workbook);

            var employeeDict = peremployeeRepository.GetEntities(t => t.AllotId == allotId);
            var collectData = collectdataRepository.GetEntities(t => t.AllotID == allotId);

            WriteDataFactory factory = new WriteDataFactory();
            for (int sheetIndex = 0; sheetIndex < workbook.NumberOfSheets; sheetIndex++)
            {
                var sheet = workbook.GetSheetAt(sheetIndex);

                var sheetType = perSheetService.GetSheetType(sheet.SheetName);
                if (sheetType == SheetType.Unidentifiable) continue;

                var point = PerSheetDataFactory.GetDataRead(sheetType)?.Point;
                if (point != null && point.DataFirstCellNum.HasValue)
                    ExtractHelper.ClearSheetPartialData(sheet, point, sheetType);

                var customer = factory.GetWriteData(sheetType);
                if (customer != null)
                {
                    string sheetName = sheet.SheetName.NoBlank();

                    var collects = collectData?.Where(t => t.SheetName.NoBlank() == sheetName).ToList();
                    customer.WriteCollectData(sheet, point, sheetType, style, collects);

                    var exdata = extractDto.Where(t => t.SheetName.NoBlank() == sheetName)?.ToList();
                    var data = GetDataBySheetType(sheetType, exdata, employeeDict);
                    customer.WriteSheetData(sheet, point, sheetType, style, data, exdict);
                }
            }
        }

        private object GetDataBySheetType(SheetType sheetType, List<ExtractTransDto> extractDto, List<per_employee> employeeDict)
        {
            switch (sheetType)
            {
                case SheetType.Employee:
                case SheetType.ClinicEmployee:
                case SheetType.AccountBasic:
                    return employeeDict;
                default:
                    return extractDto;
            }
        }

        /// <summary>
        /// 标准数据格式, 匹配科室字典
        /// </summary>
        /// <param name="hospitalId"></param>
        /// <param name="results"></param>
        /// <returns></returns>
        private List<ExtractTransDto> StandDataFormat(int hospitalId, List<ex_result> results)
        {
            if (results == null || !results.Any()) return new List<ExtractTransDto>();

            var dict = personService.GetDepartments(hospitalId)?.ToList();
            if (dict == null || !dict.Any())
                return results.Select(t => new ExtractTransDto
                {
                    SheetName = t.Source,
                    DoctorName = t.DoctorName,
                    PersonnelNumber = t.PersonnelNumber,
                    Department = t.Department,
                    Category = t.Category,
                    Value = t.Fee ?? 0
                }).ToList();

            dict.ForEach(t =>
            {
                t.HISDeptName = WriteDataHelper.HasValue(t.HISDeptName, t.Department);
            });

            var data = results.GroupJoin(dict, outer => new { Department = outer.Department }, inner => new { Department = inner.HISDeptName }, (outer, inner) => new { outer, inner })
                .Select(t =>
                {
                    var dept = !string.IsNullOrEmpty(t.inner.FirstOrDefault()?.Department) ? t.inner.FirstOrDefault()?.Department : t.outer.Department;
                    return new ExtractTransDto
                    {
                        SheetName = t.outer.Source,
                        Department = dept,
                        Category = t.outer.Category,
                        DoctorName = t.outer.DoctorName,
                        PersonnelNumber = t.outer.PersonnelNumber,
                        Value = t.outer.Fee ?? 0,
                        OutDoctorAccounting = t.inner.FirstOrDefault(f => f.Department == dept)?.OutDoctorAccounting?.AccountingUnit,
                        OutNurseAccounting = t.inner.FirstOrDefault(f => f.Department == dept)?.OutNurseAccounting?.AccountingUnit,
                        OutTechnicAccounting = t.inner.FirstOrDefault(f => f.Department == dept)?.OutTechnicAccounting?.AccountingUnit,
                        InpatDoctorAccounting = t.inner.FirstOrDefault(f => f.Department == dept)?.InpatDoctorAccounting?.AccountingUnit,
                        InpatNurseAccounting = t.inner.FirstOrDefault(f => f.Department == dept)?.InpatNurseAccounting?.AccountingUnit,
                        InpatTechnicAccounting = t.inner.FirstOrDefault(f => f.Department == dept)?.InpatTechnicAccounting?.AccountingUnit,
                    };
                });

            var groupdata = data.GroupBy(t => new { t.Department, t.Category, t.SheetName }).Select(t => new ExtractTransDto
            {
                SheetName = t.Key.SheetName,
                Department = t.Key.Department,
                Category = t.Key.Category,
                Value = t.Sum(group => group.Value) == 0 ? null : t.Sum(group => group.Value),
                OutDoctorAccounting = t.First().OutDoctorAccounting,
                OutNurseAccounting = t.First().OutNurseAccounting,
                OutTechnicAccounting = t.First().OutTechnicAccounting,
                InpatDoctorAccounting = t.First().InpatDoctorAccounting,
                InpatNurseAccounting = t.First().InpatNurseAccounting,
                InpatTechnicAccounting = t.First().InpatTechnicAccounting,
            });

            return groupdata.ToList();
        }
    }
}
