using System;
using System.Collections.Generic;
using System.Linq;
using FluentScheduler;
using Siger.Middlelayer.CncRepository;
using Siger.Middlelayer.CncRepository.Entities;
using Siger.Middlelayer.CncRepository.Response;
using Siger.Middlelayer.Common;
using Siger.Middlelayer.Common.Extensions;
using Siger.Middlelayer.Common.Helpers;
using Siger.Middlelayer.Common.Log;
using Siger.Middlelayer.Common.ModuleEnum;
using Siger.Middlelayer.Dapper;
using Siger.Middlelayer.Dapper.Utilities;
using Siger.Middlelayer.Dapper.Utilities.Slice;
using Siger.Middlelayer.Redis;
using Siger.Middlelayer.Repository.Entities;
using Siger.Middlelayer.Share.Models;
using Siger.Middlelayer.Share.Utilities;

namespace Siger.ApiCNC.Tasks
{
    public class ProduceEfficiencyJob : IJob
    {
        private static readonly ApiCncDbContext _context;

        static ProduceEfficiencyJob()
        {
            _context = new ApiCncDbContext();
        }

        public void Execute()
        {
            var now = DateTime.Now;
            if (now.Hour == 3 && now.Minute == 0)
            {
                var dbConfigs = RedisCache.Instance.GetDbNameConfigs();
                foreach (var dbNameConfig in dbConfigs)
                {
                    if (string.IsNullOrWhiteSpace(dbNameConfig.RedisDbName))
                    {
                        Logger.WriteLineError($"DbNameConfig setting error, can not find redisdbname by cid:{dbNameConfig.Cid}, pid:{dbNameConfig.Pid}.");
                        continue;
                    }

                    try
                    {
                        Calculation(dbNameConfig.Cid, dbNameConfig.Pid);                       
                    }
                    catch(Exception ex)
                    {
                        Logger.WriteLineError("SyncCncEfficiency InsertToMySql failed.error:" + ex);
                    }
                }
            }
        }

        private void Calculation(int companyId, int projectId, int syncDays = 15)
        {
            var baseDataCount =
                _context.siger_project_produce_efficiency.Count(t => t.project_id == projectId && t.status == (int)RowState.Valid);
            if (baseDataCount > 0)
            {
                syncDays = 1;
            }

            var list = new List<siger_project_produce_efficiency>();

            for (var i = syncDays; i >= 1; i--)
            {
                var dtEnd = DateTime.Today.AddDays(-i + 1).AddMilliseconds(-1).ToString(ParameterConstant.DateTimeFormat);
                var dtStart = DateTime.Today.AddDays(-i).ToString(ParameterConstant.DateTimeFormat);

                var machineIds = GetNCLevelSectionMachineIds(0, projectId).ToList();

                var end = (int)UnixTimeHelper.ConvertDataTimeLong(dtEnd.ToDateTime());

                var repository = new ProductRepository(companyId, projectId);
                var yields = repository.GetYileds(dtStart, dtEnd, machineIds).ToList();

                var sliceRep = new SliceSateRepository(companyId, projectId);
                var slices = sliceRep.GetCncSliceSates(machineIds, dtStart, dtEnd);

                var stations = GetNCLevelSectionNames(machineIds, projectId).ToList();
                var beats = _context.siger_project_beat_set.Where(q => machineIds.Contains(q.machineID) && q.projectID == projectId && q.status == (int)RowState.Valid
                                                            && q.start_time <= dtEnd.ToDateTime() && q.end_time >= dtEnd.ToDateTime()).ToList();
                var times = GetRestTimesBySections(machineIds, projectId);                
                double totalDebug = 0;
                double totalFault = 0;
                double totalShutdown = 0;
                var count = 0;

                var machineProgramNos = YieldMangaer.GetMachineProgramNos(yields, dtStart, machineIds, end);
                if (!machineProgramNos.Any())
                {
                    continue;
                }

                foreach (var machineProgramNo in machineProgramNos)
                {
                    double shutdown = 0;
                    double debug = 0;
                    double fault = 0;
                    double run = 0;
                    double idle = 0;
                    double total_efficiency = 0;

                    var station = stations.FirstOrDefault(q => q.machine_id == machineProgramNo.machineId);

                    double runTimes = 0;
                    var rests = RestTimeManager.GetRunTimeByPrograms(times, machineProgramNo, dtStart.ToDateTime(), dtEnd.ToDateTime(), ref runTimes);

                    count++;
                    var machineYield = yields.Count(q => q.machineID == machineProgramNo.machineId && q.programCode == machineProgramNo.programNo);
                    var response = new ResponseGetListEfficiencyAnalysis
                    {
                        location = station != null ? station.lastSecondSectionTitle + "-" + station.lastSectionTitle : "",
                        actual_output = machineYield,
                        idle_loss = "0%(0.00h)",
                        debug_loss = "0%(0.00h)",
                        fault_loss = "0%(0.00h)",
                        shutdown_loss = "0%(0.00h)",
                        total_efficiency = machineYield > 0 ? "100%(0.00h)" : "0%(0.00h)",
                        program_no = machineProgramNo.programNo
                    };
                    var beat = beats.FirstOrDefault(q => q.machineID == machineProgramNo.machineId && q.process_number == machineProgramNo.programNo);
                    if (beat == null)
                    {
                        response.product_name = "NA";
                        response.route_name = "NA";
                        response.theory_yield = "NA";
                        response.total_efficiency = "NA";
                        response.idle_loss = "NA";
                    }
                    else if (runTimes != 0)
                    {
                        response.actual_output = response.actual_output * beat.yieldrate;
                        response.product_name = beat.product_name_text;
                        response.route_name = beat.route_name;

                        var machineSlice = slices.Where(q => q.MachineID == machineProgramNo.machineId);
                        //0 关机 1 运行 2 调试 3 空闲 4 故障
                        shutdown = SliceManager.GetResetTimesBySlice(0, machineProgramNo.TimeRanges, machineSlice, rests);
                        debug = SliceManager.GetResetTimesBySlice(2, machineProgramNo.TimeRanges, machineSlice, rests);
                        fault = SliceManager.GetResetTimesBySlice(4, machineProgramNo.TimeRanges, machineSlice, rests);
                        run = SliceManager.GetResetTimesBySlice(1, machineProgramNo.TimeRanges, machineSlice, null);

                        runTimes = runTimes < 0 ? run : runTimes;

                        totalDebug += debug / runTimes;
                        totalFault += fault / runTimes;
                        totalShutdown += shutdown / runTimes;

                        response.debug_loss_value = Math.Round(debug / runTimes * 100, 2);
                        response.debug_loss = response.debug_loss_value.ToStr() + "%(" + Math.Round(debug / 3600, 2) + "h)";

                        response.fault_loss_value = Math.Round(fault / runTimes * 100, 2);
                        response.fault_loss = response.fault_loss_value.ToStr() + "%(" + Math.Round(fault / 3600, 2) + "h)";

                        response.shutdown_loss_value = Math.Round(shutdown / runTimes * 100, 2);
                        response.shutdown_loss = response.shutdown_loss_value.ToStr() + "%(" + Math.Round(shutdown / 3600, 2) + "h)";

                        var beatInfo = beat.standard_besat + beat.updown_besat;
                        response.theory_yield = beatInfo <= 0 ? "0" : (Math.Round(runTimes / beatInfo, 0) * beat.yieldrate).ToStr();

                        idle = runTimes - shutdown - debug - fault - beatInfo * machineYield;
                        var userIdle = idle < 0 ? 0 : idle;

                        response.idle_loss_value = Math.Round(userIdle / runTimes * 100, 2);
                        response.idle_loss = response.idle_loss_value.ToStr() + "%(" + Math.Round(userIdle / 3600, 2) + "h)";

                        total_efficiency = beatInfo * machineYield;
                        response.total_efficiency_value = Math.Round(beatInfo * machineYield / runTimes * 100, 2);
                        response.total_efficiency = response.total_efficiency_value.ToStr()
                                                    + "%(" + Math.Round(beatInfo * machineYield / 3600, 2) + "h)";
                    }

                    var nowToday = DateTime.Today.AddDays(-i);
                    var entity = _context.siger_project_produce_efficiency.FirstOrDefault(t =>
                        t.project_id == projectId && t.status == (int) RowState.Valid &&
                        t.machine_id == machineProgramNo.machineId && t.date >= nowToday &&
                        t.date < nowToday.AddDays(1));
                    if (entity != null)
                    {
                        continue;
                    }

                    list.Add(new siger_project_produce_efficiency
                    {
                        machine_id = machineProgramNo.machineId,
                        machine_location = response.location,
                        program_no = response.program_no,
                        product_name = response.product_name,
                        route_name = response.route_name,
                        theory_yield = response.theory_yield,
                        actual_yield = response.actual_output,
                        total_efficiency = total_efficiency,
                        idle_loss = idle,
                        debug_loss = debug,
                        fault_loss = fault,
                        shutdown_loss = shutdown,
                        run_times = runTimes,
                        create_time = DateTime.Now,
                        date = DateTime.Now.AddDays(-i),
                        project_id = projectId
                    });
                }
            }

            try
            {
                if (list.Any())
                {
                    _context.siger_project_produce_efficiency.AddRange(list);
                    _context.SaveChanges();
                }
            }
            catch (Exception e)
            {
                Logger.WriteLineError("SyncCncEfficiency InsertToMySql failed.error:" + e);
            }
        }

        private IEnumerable<int> GetNCLevelSectionMachineIds(int id, int projectid)
        {
            var list = new List<int>();
            var query = GetSonLevelSections(id, projectid);

            list.Add(id);
            foreach (var section in query.ToList())
            {
                list.Add(section.id);
            }

            var querylist = from q in _context.siger_project_machine_attribution
                join m in _context.siger_project_machine on q.machine equals m.id into ma
                from m in ma.DefaultIfEmpty()
                where list.Contains(q.station) && q.status == (int)RowState.Valid &&
                      m.status == (int)RowState.Valid
                      && m.projectid == projectid && m.category == (int)MachineCategory.NC
                select q.machine;

            return querylist.ToList();
        }

        private IEnumerable<siger_project_level_section> GetSonLevelSections(int parentId, int projectid)
        {
            var query = from c in _context.siger_project_level_section
                where c.parentid == parentId && c.projectid == projectid && c.status == (int)RowState.Valid
                select c;

            return query.ToList().Concat(query.ToList().SelectMany(t => GetSonLevelSections(t.id, projectid)));
        }

        private IEnumerable<MachineData> GetNCLevelSectionNames(IEnumerable<int> machineIds, int projectid)
        {
            var querylist = from q in _context.siger_project_machine_attribution
                join m in _context.siger_project_machine on q.machine equals m.id
                join se in _context.siger_project_level_section on q.station equals se.id
                join se2 in _context.siger_project_level_section on se.parentid equals se2.id
                where machineIds.Contains(q.machine) && q.status == (int)RowState.Valid &&
                      m.status == (int)RowState.Valid && se.status == (int)RowState.Valid
                      && m.projectid == projectid && m.category == (int)MachineCategory.NC
                select new MachineData
                {
                    machine_id = m.id,
                    machine_name = m.title,
                    machine_code = m.code,
                    section_id = se.id,
                    lastSectionTitle = se.title,
                    lastSecondSectionTitle = se2.title
                };

            return querylist.ToList();
        }

        private IEnumerable<MachineRestInfo> GetRestTimesBySections(IEnumerable<int> machineIds, int projectid)
        {
            var result = new List<MachineRestInfo>();

            var times = _context.siger_project_production_time.Where(m => machineIds.Contains(m.machineid) && m.status == (int)RowState.Valid && m.projectid == projectid).ToList();
            foreach (var machineId in machineIds)
            {
                var machineTimes = times.Where(q => q.machineid == machineId);
                foreach (var time in machineTimes)
                {
                    result.Add(new MachineRestInfo
                    {
                        BingTime = time.bing_time,
                        EndTime = time.end_time,
                        MachineId = machineId,
                        TimeType = (TimeType)time.typeid
                    });
                }
            }

            return result;
        }
    }
}
