using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using FluentScheduler;
using Siger.Middlelayer.Common;
using Siger.Middlelayer.DashboardRepository;
using Siger.Middlelayer.Repository.Entities;
using Siger.Middlelayer.DashboardRepository.Entities;
using Siger.Middlelayer.Log;
using Siger.Middlelayer.Redis;
using Siger.Middlelayer.Dapper;
using Siger.Middlelayer.DashboardRepository.Response;
using Siger.Middlelayer.Common.Helpers;
using Siger.Middlelayer.Common.ModuleEnum;
using Siger.Middlelayer.TpmRepository;
using Siger.Middlelayer.QmsRepository;
using Siger.Middlelayer.Share.Enum.ModuleEnum;
using Siger.Middlelayer.KpiRespository;
using Siger.Middlelayer.TpmRepository.Entities;
using Siger.Middlelayer.Share.ModuleEnum;
using Siger.ApiDashboard.Utilities;
using Siger.Middlelayer.Common.Extensions;
using Siger.DataSchedule.Infrastructure.Extend;
using Siger.ApiDashboard.Tasks;
using Siger.Middlelayer.QmsRepository.Response;
using Siger.Middlelayer.QmsRepository.Entities;
using Siger.Middlelayer.Repository.Extensions;
using siger_andon_info = Siger.Middlelayer.TpmRepository.Entities.siger_andon_info;

namespace Siger.Schedule.DashboardModule
{
    public class AutoTaskJobcsSchedule : IScheduleJob
    {
        AutoTaskJob andonHelper;
        public void PreInit(ScheduleContext context)
        {
            Logger.RegisterLogEngine(Module.Dashboard);
            andonHelper = new AutoTaskJob();
        }
        public void Execute(ScheduleContext context)
        {
            andonHelper.Execute();
        }


        public void AfterExecute(ScheduleContext context)
        {

        }


        public void Undo()
        {

        }
    }

    public class AutoTaskJob : IJob
    {
        private static ApiDashboardDbContext _context;
        private static ApiTpmDbContext _tpmDbContext;
        private static ApiQmsDbContext _qmsDbContext;
        private static ApiKpiDbContext _apiDbContext;

        public AutoTaskJob()
        {
            _context = new ApiDashboardDbContext();
            _qmsDbContext = new ApiQmsDbContext();
            _tpmDbContext = new ApiTpmDbContext();
            _apiDbContext = new ApiKpiDbContext();
        }

        public void Execute()
        {
            try
            {
                var dbConfigs = RedisCache.Instance.GetDbNameConfigs();
                foreach (var dbNameConfig in dbConfigs)
                {
                    if (string.IsNullOrWhiteSpace(dbNameConfig.RedisDbName))
                    {
                        Logger.WriteLineError($"AutoTaskJob DbNameConfig setting error, can not find redisdbname by cid:{dbNameConfig.Cid}, pid:{dbNameConfig.Pid}.");
                        continue;
                    }
                    if (dbNameConfig.Pid != 160)
                        continue;
                    TaskAutoCalculation(dbNameConfig.Cid, dbNameConfig.Pid);

                    TaskAutoCalculationHour(dbNameConfig.Cid, dbNameConfig.Pid);
                }
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"AutoTaskJob DbNameConfig setting error {e.StackTrace}");
            }
        }
        /// <summary>
        /// 每小时统计
        /// </summary>
        /// <param name="companyId"></param>
        /// <param name="projectId"></param>
        private void TaskAutoCalculationHour(int companyId, int projectId)
        {

            var dateTime = DateTime.Now.AddHours(-1);
            var busidate = dateTime.Date;
            var currHour = dateTime.Hour;

            var begin = busidate.AddHours(currHour);
            var endtime = begin.AddHours(1);

            CalculationOee(companyId, projectId, busidate, begin, endtime, false);
        }
        /// <summary>
        /// 汇总前一日数据
        /// </summary>
        /// <param name="companyId"></param>
        /// <param name="projectId"></param>
        private void TaskAutoCalculation(int companyId, int projectId)
        {
            var busidate = DateTime.Now.AddDays(-1).Date;
            var begin = busidate.AddHours(8);
            var endtime = begin.AddHours(24);

            CalculationOee(companyId, projectId, busidate, begin, endtime);
            CalculationProductive(companyId, projectId, busidate, begin, endtime);
            CalculationFtq(companyId, projectId, busidate, begin, endtime);
            CalculationMttrbf(companyId, projectId, busidate, begin, endtime);
            CalculationWaste(projectId, busidate, begin, endtime);
            CalculationKpiInfo(projectId, busidate, begin, endtime);
        }

        private void CalculationMttrbf(int companyId, int projectId, DateTime busidate, DateTime begin, DateTime end)
        {
            try
            {
                var lines = GetLine(projectId).ToList();
                foreach (var line in lines)
                {
                    var machine = new List<int>();
                    var data = StationMttrbf(projectId, machine, begin, end);
                    if (data.Any())
                    {
                        if (data.Count > 3)
                        {
                            SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.MTTR, begin, end, data[0], 0);
                            SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.MTBF, begin, end, data[1], 0);
                            SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.Maintance, begin, end, data[2], 0);
                            SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.FultTime, begin, end, data[3], 0);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"CalculationMttrbf{e.StackTrace}");
            }

        }

        /// <summary>
        ///  产线 综合OEE
        /// </summary>
        /// <param name="companyId"></param>
        /// <param name="projectId"></param>
        /// <param name="busidate"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="day">是否按天统计</param> 
        private void CalculationOee(int companyId, int projectId, DateTime busidate, DateTime begin, DateTime end, bool day = true)
        {
            try
            {
                //
                var yieldRepository = new LocationYieldRepository(companyId, projectId);
                var lines = GetLine(projectId).ToList();
                foreach (var line in lines)
                {
                    var bottleMachine = GetBottleMachine(projectId, line.id);
                    var YeidData = yieldRepository.GetLocationYields(new List<int> { bottleMachine }, begin, end).ToList();
                    //理论值
                    var targetttl = GetTargetVal(projectId, bottleMachine, begin, end);

                    //产出
                    var yeidttl = YeidData.Sum(s => s.yield);

                    var oee = GetOee(UnixTimeHelper.ConvertDataTimeLong(begin.ToString()), UnixTimeHelper.ConvertDataTimeLong(end.ToString()), line.id, projectId, companyId);
                    if (day == true)
                    {
                        SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.OEE, begin, end, oee.Molecular, oee.Denominator);
                        SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.ActYeid, begin, end, yeidttl, targetttl);
                    }
                    else
                    {
                        SaveDashboardHourTotal(busidate, projectId, line.id, DashboardTotalRate.OEE, begin, end, oee.Molecular, oee.Denominator);
                    }
                }
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"AutoTaskJob CalculationOee " + e.ToString());
            }
        }

        private double GetTargetVal(int projectId, int bottleMachine, DateTime begin, DateTime end)
        {
            var targetttl = 0d;
            DateTime dtDay;
            for (dtDay = begin; dtDay.CompareTo(end) < 0; dtDay = dtDay.AddHours(1))
            {
                var stepStarTime = dtDay;
                var stepEndTime = dtDay.AddHours(1).AddSeconds(-1);
                var targetVal = BottleProductTarget(projectId, bottleMachine, stepStarTime, stepEndTime);
                if (targetVal == null)
                    continue;
                targetttl += targetVal.Val;
            }

            return targetttl;
        }

        /// <summary>
        /// 产量达成率
        /// </summary>
        /// <param name="companyId"></param>
        /// <param name="projectId"></param>
        /// <param name="busidate"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        private void CalculationProductive(int companyId, int projectId, DateTime busidate, DateTime begin, DateTime end)
        {
            try
            {
                var yieldRepository = new LocationYieldRepository(companyId, projectId);
                var lines = GetLine(projectId).ToList();
                foreach (var line in lines)
                {
                    var bottleMachine = GetBottleMachine(projectId, line.id);
                    var YeidData = yieldRepository.GetLocationYields(new List<int> { bottleMachine }, begin, end).ToList();
                    var targetttl = GetTargetVal(projectId, bottleMachine, begin, end);
                    //产出
                    var yeidttl = YeidData.Sum(s => s.yield);
                    SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.YieldAchieved, begin, end, yeidttl, targetttl);
                }
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"AutoTaskJob CalculationProductive " + e);
            }
        }

        /// <summary>
        /// 通道一次检验合格率
        /// </summary>
        /// <param name="companyId"></param>
        /// <param name="projectId"></param>
        /// <param name="busidate"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        private void CalculationFtq(int companyId, int projectId, DateTime busidate, DateTime begin, DateTime end)
        {
            try
            {
                var _yieldRepository = new LocationYieldRepository(companyId, projectId);

                var data = GetSnTraceList(new List<int> { }, 0, 0, 0, begin.ToString(ParameterConstant.DateTimeFormat), end.ToString(ParameterConstant.DateTimeFormat), projectId).ToList();
                var lines = GetLine(projectId).ToList();
                foreach (var line in lines)
                {
                    var sections = GetStation(projectId, line.id).ToList();
                    var yieldDatas = _yieldRepository.GetLocationYieldsBySectionIds(new List<int> { line.id }, begin, end).ToList();
                    //var yieldDatas = _yieldRepository.GetLocationYieldsBySectionIds(sections.Select(t => t.id), begin, end).ToList();
                    //var bottleMachine = GetBottleMachine(projectId, line.id,false);

                    var list = data.Where(t => sections.Select(q => q.id).Contains(t.SectionID)).ToList();
                    double okRate = 1;
                    //var minOk = 0d;
                    //var minTtl = 0d;
                    var yieldCount = 0;
                    var recordCount = 0d;
                    var yield = yieldDatas.Sum(t => t.yield);
                    foreach (var _section in sections)
                    {
                        var ngNum = list.Count(t => t.SectionID == _section.id && t.Result.ToUpper() == "NG");
                        if (yield == 0)
                            continue;
                        var actualYield = (yield - ngNum) > 0 ? (yield - ngNum) : 0;
                        var rate = yield > 0 ? actualYield / (double)yield : 0;
                        if (rate < okRate)
                        {
                            okRate = rate;
                        }
                        yieldCount += actualYield;
                        recordCount += yield;
                    }


                    SaveDashboardTotal(busidate, projectId, line.id, DashboardTotalRate.Qualified, begin, end, yieldCount, recordCount);
                }
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"AutoTaskJob CalculationFtq " + e);
            }
        }

        private IEnumerable<SnTrace> GetSnTraceList(List<int> sectionIds, int productid, int materialid, int checktype, string starttime, string endtime, int projectid)
        {
            var query = from q in _qmsDbContext.siger_check_sn_trace_inspection
                            //join t in _context.siger_check_sn_trace_inspection on q.TraceID equals t.trace_id
                        where q.projectid == productid && q.status == (int)RowState.Valid //&& t.status == (int)RowState.Valid && q.projectid == productid
                        && q.check_status == (int)SendCheckStatus.Completed
                        select new SnTrace
                        {
                            ID = q.trace_id,
                            ProductID = q.productid,
                            MaterialID = q.materialid,
                            SectionID = q.sectionid,
                            WorkOrder = q.workorder,
                            SN = q.sn,
                            Result = q.result == ((int)SendTestType.Unqualified).ToString() ? "NG" : "OK",
                            UserID = q.checking_mid,
                            CreateTime = q.check_time > DateTime.MinValue ? q.check_time.Value : DateTime.MinValue,
                            RouteID = q.routeid,
                            CheckType = q.check_type,
                        };

            Expression<Func<SnTrace, bool>> materialExpression = q => true;
            if (materialid > 0)
            {
                materialExpression = q => q.MaterialID == materialid;
            }
            Expression<Func<SnTrace, bool>> sectionExpression = q => true;
            if (sectionIds.Any())
            {
                sectionExpression = q => sectionIds.Contains(q.SectionID);
            }
            Expression<Func<SnTrace, bool>> productExpression = q => true;
            if (productid > 0)
            {
                productExpression = q => q.ProductID == productid;
            }
            Expression<Func<SnTrace, bool>> checkTypeExpression = q => true;
            if (checktype > 0)
            {
                checkTypeExpression = q => q.CheckType == checktype;
            }
            Expression<Func<SnTrace, bool>> timeExpression = q => true;
            if (!string.IsNullOrEmpty(starttime) && !string.IsNullOrEmpty(endtime))
            {
                timeExpression = q => q.CreateTime <= endtime.ToDateTime() && q.CreateTime >= starttime.ToDateTime();
            }
            var expression = materialExpression.And(sectionExpression).And(productExpression).And(checkTypeExpression).And(timeExpression);

            return query.Where(expression);
        }

        /// <summary>
        /// 设备MTTR,MTBF,停机时间,设备维护用时比
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="machines"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        private List<double> StationMttrbf(int projectId, List<int> machines, DateTime begin, DateTime end)
        {

            var starttime = (int)UnixTimeHelper.ConvertDataTimeLong(begin);
            var endtime = (int)UnixTimeHelper.ConvertDataTimeLong(end);

            var repairs = _tpmDbContext.siger_project_repair.Where(q => q.createtime >= starttime && q.createtime <= endtime && machines.Contains(q.machineid)
                                                                        && q.projectid == projectId && q.status == (int)MachineRepairStatus.Completed).ToList();
            var list = new List<double>
            {
                MTTRBFHelper.GetMachineMTTRBF(begin, end, repairs, 1),
                MTTRBFHelper.GetMachineMTTRBF(begin, end, repairs, 1, 2),
                repairs.Sum(q => q.checktime - q.createtime)
            };

            //设备维护用时比
            var plans = (from q in _tpmDbContext.siger_project_plan_record
                         join item in _tpmDbContext.siger_project_plan_item on q.itemid equals item.id
                         where q.status == (int)RowState.Valid && item.status == (int)RowState.Valid
                                                                && q.create_time >= starttime && q.create_time <= endtime
                                                                && q.projectid == projectId
                         select new
                         {
                             act_standard_time = q.actual_standard_time,
                             standard_time = item.standard_time
                         }).ToList();
            var acts = 0;
            var standards = 0;
            if (plans.Any())
            {
                foreach (var result in plans)
                {
                    acts += result.act_standard_time;
                    standards += result.standard_time;
                }
            }

            list.Add(standards == 0 ? 0 : Math.Round((double)acts / standards * 100, 4));
            return list;
        }

        /// <summary>
        /// 报废/返工汇总
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="busidate"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        private void CalculationWaste(int projectId, DateTime busidate, DateTime begin, DateTime end)
        {
            try
            {
                var lines = GetLine(projectId);
                foreach (var l in lines)
                {
                    var stations = GetSonLevelSections(l.id, projectId).Select(s => s.id).ToList();
                    var data = _qmsDbContext.siger_qms_rework_data.Where(f => f.projectid == projectId && stations.Contains(f.Section_Id) && f.DateTime >= begin && f.DateTime <= end);
                    var wastettl = data.Where(f => f.Type == (int)ReworkDataType.Waste).Sum(s => s.Count);
                    var reworkttl = data.Where(f => f.Type == (int)ReworkDataType.Rework).Sum(s => s.Count);
                    SaveDashboardTotal(busidate, projectId, l.id, DashboardTotalRate.Waste, begin, end, wastettl, 0);
                    SaveDashboardTotal(busidate, projectId, l.id, DashboardTotalRate.Rework, begin, end, reworkttl, 0);
                }
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"CalculationWaste{e.StackTrace}");
            }

        }

        /// <summary>
        /// 各KPI 指标
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="busidate"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        private void CalculationKpiInfo(int projectId, DateTime busidate, DateTime begin, DateTime end)
        {
            var lines = GetLine(projectId).FirstOrDefault();
            if (lines == null)
                return;
            var factory = lines.parentid;
            var safeday = GetKpiSafeDay(KpiItemNameKey.SafeDay, projectId, new List<int> { }, begin, end);
            SaveDashboardTotal(busidate, projectId, factory, DashboardTotalRate.ZeroAcciden, begin, end, safeday, 1);

            var comlaint = GetKpiComplaint(KpiItemNameKey.Complaint, projectId, new List<int> { }, begin, end);
            SaveDashboardTotal(busidate, projectId, factory, DashboardTotalRate.Complaint, begin, end, comlaint, 0);
        }
        /// <summary>
        /// 更新通道+时间段汇总
        /// </summary>
        /// <param name="busidate"></param>
        /// <param name="projectId"></param>
        /// <param name="section">产线/通道</param>
        /// <param name="ratetype"></param>
        /// <param name="stime"></param>
        /// <param name="etime"></param>
        /// <param name="numerator"></param>
        /// <param name="denominator"></param>
        /// 
        private void SaveDashboardTotal(DateTime busidate, int projectId, int section, DashboardTotalRate ratetype, DateTime stime, DateTime etime, double numerator, double denominator)
        {
            try
            {
                var ttlObj = _context.siger_project_auto_calculation_data_ttl.FirstOrDefault(f => f.projectid == projectId && f.busidate == busidate && f.section == section && f.ratetype == ratetype);
                if (ttlObj == null)
                {
                    _context.siger_project_auto_calculation_data_ttl.Add(new siger_project_auto_calculation_data_ttl
                    {
                        busidate = busidate,
                        ratetype = ratetype,
                        status = (int)RowState.Valid,
                        projectid = projectId,
                        section = section,
                        starttime = stime,
                        endtime = etime,
                        numerator = numerator,
                        denominator = denominator,
                        lastupdate = DateTime.Now,
                    });
                }
                else
                {
                    ttlObj.numerator = numerator;
                    ttlObj.denominator = denominator;
                    ttlObj.lastupdate = DateTime.Now;
                    _context.siger_project_auto_calculation_data_ttl.Update(ttlObj);
                }
                _context.SaveChanges();
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"SaveDashboardTotal{e.StackTrace}");
            }
        }

        private void SaveDashboardHourTotal(DateTime busidate, int projectId, int section, DashboardTotalRate ratetype, DateTime stime, DateTime etime, double numerator, double denominator)
        {
            try
            {
                var ttlObj = _context.siger_project_auto_calculation_data_hour_ttl.FirstOrDefault(f => f.projectid == projectId && f.busidate == busidate && f.starttime == stime && f.section == section && f.ratetype == ratetype);
                if (ttlObj == null)
                {
                    _context.siger_project_auto_calculation_data_hour_ttl.Add(new siger_project_auto_calculation_data_hour_ttl
                    {
                        busidate = busidate,
                        ratetype = ratetype,
                        status = (int)RowState.Valid,
                        projectid = projectId,
                        section = section,
                        starttime = stime,
                        endtime = etime,
                        numerator = numerator,
                        denominator = denominator,
                        lastupdate = DateTime.Now,
                    });
                }
                else
                {
                    ttlObj.numerator = numerator;
                    ttlObj.denominator = denominator;
                    ttlObj.lastupdate = DateTime.Now;
                    _context.siger_project_auto_calculation_data_hour_ttl.Update(ttlObj);
                }
                _context.SaveChanges();
            }
            catch (Exception e)
            {
                Logger.WriteLineError($"SaveDashboardTotal{e.StackTrace}");
            }
        }
        /// <summary>
        /// 工厂产线
        /// </summary>
        /// <param name="projectId"></param>
        /// <returns></returns>
        public IEnumerable<siger_project_level_section> GetLine(int projectId)
        {
            var linelevel = _context.siger_project_level.Where(f => f.projectid == projectId && f.status == (int)RowState.Valid).OrderByDescending(o => o.id).ToList();
            if (!linelevel.Any() || linelevel.Count < 2)
                return new List<siger_project_level_section>();
            var lineSetion = _context.siger_project_level_section.Where(f => f.projectid == projectId && f.levelid == linelevel[1].id && f.status == (int)RowState.Valid);
            return lineSetion;
        }
        public IEnumerable<siger_project_level_section> GetStation(int projectId, int line)
        {
            var linelevel = _context.siger_project_level.Where(f => f.projectid == projectId && f.status == (int)RowState.Valid).OrderByDescending(o => o.id).ToList();
            if (!linelevel.Any() || linelevel.Count < 2)
                return new List<siger_project_level_section>();
            var stationSetion = _context.siger_project_level_section.Where(f => f.projectid == projectId && f.levelid == linelevel[0].id && f.parentid == line && f.status == (int)RowState.Valid);
            return stationSetion;
        }

        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<int> GetAttributionMachineIds(int id, int projectid)
        {
            var list = new List<int>();
            var query = GetSonLevelSections(id, projectid).ToList();
            if (!query.Any())
            {
                list.Add(id);
            }
            else
            {
                foreach (var section in query)
                {
                    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.attribution < 4
                            orderby m.sorting
                            select q.machine;

            return querylist.ToList();
        }

        /// <summary>
        /// 产线瓶颈工位 
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="line"></param>
        /// <param name="mid"></param>
        /// <returns></returns>
        private int GetBottleMachine(int projectId, int line, bool mid = true)
        {
            var machineids = GetAttributionMachineIds(line, projectId);
            var query = from cfg in _context.siger_andon_station_config
                        join attr in _context.siger_project_machine_attribution on cfg.machine_id equals attr.machine
                        where machineids.Contains(cfg.machine_id) && cfg.project_id == projectId &&
                        attr.attribution == 1 &&
                        cfg.station_type == 1 &&
                        cfg.status == (int)RowState.Valid && attr.status == (int)RowState.Valid
                        select new ResponseLevelSectionMachine
                        {
                            Id = cfg.id,
                            MachineId = cfg.machine_id,
                            Section = attr.station,
                            Title = attr.name
                        };
            //瓶颈工位 
            if (mid)
                return query.Select(s => s.MachineId).FirstOrDefault();
            else
                return query.Select(s => s.Section).FirstOrDefault();

        }

        /// <summary>
        ///  产品目标产量
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="second"></param>
        /// <param name="productName"></param>
        /// <returns></returns>
        public double ProductTarget(int projectId, double second, string productName)
        {
            var product = _context.siger_project_product.FirstOrDefault(f => f.projectid == projectId && f.name == productName);
            if (product == null)
                return 0;
            var productRoutes = _context.siger_project_product_route.Where(f => f.projectId == projectId && f.productId == product.id && f.status == (int)RowState.Valid).OrderByDescending(d => d.serialNumber);
            if (!productRoutes.Any())
                return 0;
            var maxRote = productRoutes.FirstOrDefault();
            var rated = maxRote?.working_hours ?? 0;
            var count = rated == 0 ? 0 : Math.Abs(second / rated);
            return Math.Round(count);
        }

        private ResponseProductTarget BottleProductTarget(int projectId, int bottleMachine, DateTime stepStartDate, DateTime stepEndDate)
        {
            var _targetVal = 0d;
            var _targetPd = "";
            DateTime dtDay;
            var dtMonth = stepStartDate.AddDays(-60);
            for (dtDay = stepStartDate; dtDay.CompareTo(stepEndDate) < 0; dtDay = dtDay.AddHours(1))
            {
                var currMin = DateTime.Now.Minute;
                var stepStarTime = dtDay;
                var stepEndTime = dtDay.AddHours(1).AddSeconds(-1);
                if (stepEndTime > DateTime.Now)
                    stepEndTime = DateTime.Now;
                var productAndonHour = GetAndonProductName(projectId, bottleMachine, stepStarTime, stepEndTime);
                var productWorkingTime = (stepEndTime - stepStarTime).TotalSeconds;
                if (stepStarTime > DateTime.Now)
                    continue;
                if (DateTime.Now.Hour == stepStarTime.Date.Hour)
                    productWorkingTime = DateTime.Now.Minute * 60 + DateTime.Now.Second;
                if (productAndonHour.Any()) // 有安灯换型
                {
                    var balaceHour = 0d;
                    foreach (var p in productAndonHour)
                    {
                        if (p.datetime >= stepStarTime)
                        {//取最后一次安灯换型
                            balaceHour = (p.datetime - stepStarTime).TotalSeconds;
                            var preProduct = GetAndonLastChangeProduct(projectId, bottleMachine, dtMonth, stepStarTime);
                            if (preProduct != null)
                            {
                                _targetVal += ProductTarget(projectId, balaceHour, preProduct.productName);
                                _targetPd += $" {preProduct.productName},";
                            }
                            continue;
                        }
                    }

                    var ptime = 0d;
                    var endStepTime = stepEndTime;
                    if (stepEndTime > DateTime.Now)
                    {
                        endStepTime = DateTime.Now;
                        ptime = (currMin * 60) - balaceHour;
                    }
                    else
                    {
                        ptime = 3600 - balaceHour;
                    }
                    ptime = GetStopTime(projectId, ptime, bottleMachine, stepStarTime, stepEndTime);
                    if (ptime > 0)
                    {
                        var lastProduct = GetAndonLastChangeProduct(projectId, bottleMachine, dtMonth, endStepTime);
                        if (lastProduct != null)
                        {
                            _targetVal += ProductTarget(projectId, ptime, lastProduct.productName);
                            _targetPd += $"{lastProduct.productName},";
                        }
                    }
                }
                else
                {
                    productWorkingTime = GetStopTime(projectId, productWorkingTime, bottleMachine, stepStarTime, stepEndTime);
                    //取最后一次安灯换型
                    var lastProduct = GetAndonLastChangeProduct(projectId, bottleMachine, dtMonth, stepStarTime);
                    if (lastProduct != null)
                    {
                        _targetVal += ProductTarget(projectId, productWorkingTime, lastProduct.productName);
                        _targetPd += $" {lastProduct.productName},";
                    }
                }
            }
            if (_targetPd.Length > 0)
                _targetPd = _targetPd.Substring(0, _targetPd.Length - 1);
            return new ResponseProductTarget
            {
                Name = _targetPd,
                Val = _targetVal
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="machineIds"></param>
        /// <param name="stime"></param>
        /// <param name="etime"></param>
        /// <returns></returns>
        public ResponseAndoProductType GetAndonLastChangeProduct(int projectId, int machineIds, DateTime stime, DateTime etime)
        {
            var types = _context.siger_andon_expection_type.Where(f => f.projectid == projectId && f.is_passprocess == 1 && f.is_caloee == 1).Select(s => s.id).ToList();
            var _stime = UnixTimeHelper.ConvertDataTimeLong(stime);
            var _etime = UnixTimeHelper.ConvertDataTimeLong(etime);
            var query = from a in _context.siger_andon_info
                        join p in _context.siger_project_product on a.product_code equals p.code
                        where a.projectid == projectId &&
                              machineIds == a.machine && types.Contains(a.expection_type) &&
                              a.create_time >= _stime && a.create_time <= _etime
                        select new ResponseAndoProductType
                        {
                            hour = UnixTimeHelper.ConvertStringDateTime(a.create_time.ToString()).Hour,
                            productCode = p.code,
                            productName = p.name,
                            datetime = UnixTimeHelper.ConvertStringDateTime(a.create_time.ToString())
                        };
            var data = query.OrderByDescending(o => o.datetime);
            return data.FirstOrDefault();
        }

        /// <summary>
        /// 获取瓶颈工位 计划停机
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="time"></param>
        /// <param name="mid"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public double GetStopTime(int projectId, double time, int mid, DateTime begin, DateTime end)
        {
            //计划停机类型
            var types = _context.siger_andon_expection_type.Where(f => f.projectid == projectId && f.is_passprocess == 1 && f.is_caloee == 0).Select(s => s.id).ToList();
            var _stime = UnixTimeHelper.ConvertDataTimeLong(begin);
            var _etime = UnixTimeHelper.ConvertDataTimeLong(end);

            //时间之前未关闭
            Expression<Func<siger_andon_info, bool>> FunUnClosed = f => f.projectid == projectId && types.Contains(f.expection_type) && f.machine == mid && f.create_time < _stime && f.approve_time == 0;
            var andonUnClosed = _context.siger_andon_info.FirstOrDefault(FunUnClosed);
            if (andonUnClosed != null)
                return 0;
            //时间段之内未关闭
            Expression<Func<siger_andon_info, bool>> FunUnClosedBet = f => f.projectid == projectId && types.Contains(f.expection_type) && f.machine == mid && f.create_time > _stime && f.create_time < _etime && f.approve_time == 0;
            var andoUnClosedBet = _context.siger_andon_info.FirstOrDefault(FunUnClosedBet);
            if (andoUnClosedBet != null)
            {
                var ctime = UnixTimeHelper.ConvertStringDateTime(andoUnClosedBet.create_time.ToString());
                var attotal = (end - ctime).TotalSeconds;
                return time - attotal;
            }
            //覆盖时间段 已关闭
            Expression<Func<siger_andon_info, bool>> FunBL = f => f.projectid == projectId && types.Contains(f.expection_type) && f.machine == mid && f.create_time <= _stime && f.approve_time >= _etime && f.approve_time != 0;
            var andonBl = _context.siger_andon_info.FirstOrDefault(FunBL);
            if (andonBl != null)
                return 0;

            //时间段之内 已关闭
            Expression<Func<siger_andon_info, bool>> FunClosed = f => f.projectid == projectId && types.Contains(f.expection_type) && f.machine == mid && f.create_time >= _stime && f.approve_time <= _etime && f.approve_time != 0;
            var andonClosedlst = _context.siger_andon_info.Where(FunClosed);
            var attotallst = 0d;

            foreach (var andon in andonClosedlst)
            {
                var createtime = UnixTimeHelper.ConvertStringDateTime(andon.create_time.ToString());
                var comptime = UnixTimeHelper.ConvertStringDateTime(andon.approve_time.ToString());
                var tspan = (comptime - createtime).TotalSeconds;
                attotallst += tspan;

            }

            //时间端左侧
            Expression<Func<siger_andon_info, bool>> FunClosed2 = f => f.projectid == projectId && types.Contains(f.expection_type) && f.machine == mid && f.create_time < _stime && f.approve_time > _stime && f.approve_time <= _etime && f.approve_time != 0;
            var andonClosedlst2 = _context.siger_andon_info.Where(FunClosed2);
            foreach (var andon in andonClosedlst2)
            {
                var comptime = UnixTimeHelper.ConvertStringDateTime(andon.approve_time.ToString());
                var tspan = (comptime - begin).TotalSeconds;
                attotallst += tspan;
            }
            //时间端右侧
            Expression<Func<siger_andon_info, bool>> FunClosed3 = f => f.projectid == projectId && types.Contains(f.expection_type) && f.machine == mid && f.create_time > _stime && f.create_time <= _etime && f.approve_time > _etime && f.approve_time != 0;
            var andonClosedlst3 = _context.siger_andon_info.Where(FunClosed3);
            foreach (var andon in andonClosedlst3)
            {
                var createtime = UnixTimeHelper.ConvertStringDateTime(andon.create_time.ToString());
                var tspan = (end - createtime).TotalSeconds;
                attotallst += tspan;
            }

            return time - attotallst;
        }

        /// <summary>
        /// 获取时间段换型产品
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="machineId"></param>
        /// <param name="stime"></param>
        /// <param name="etime"></param>
        /// <returns></returns>
        public IEnumerable<ResponseAndoProductType> GetAndonProductName(int projectId, int machineId, DateTime stime, DateTime etime)
        {
            var _stime = UnixTimeHelper.ConvertDataTimeLong(stime);
            var _etime = UnixTimeHelper.ConvertDataTimeLong(etime);
            var types = _context.siger_andon_expection_type.Where(f => f.projectid == projectId && f.is_passprocess == 1 && f.is_caloee == 1).Select(s => s.id).ToList();

            var query = from a in _context.siger_andon_info
                        join p in _context.siger_project_product on a.product_code equals p.code
                        where a.projectid == projectId &&
                              machineId == a.machine && types.Contains(a.expection_type) &&
                              a.create_time >= _stime && a.create_time <= _etime
                        select new ResponseAndoProductType
                        {
                            hour = UnixTimeHelper.ConvertStringDateTime(a.create_time.ToString()).Hour,
                            productCode = p.code,
                            productName = p.name,
                            datetime = UnixTimeHelper.ConvertStringDateTime(a.create_time.ToString())
                        };
            return query.OrderBy(f => f.datetime);
        }


        /// <summary>
        /// 安全天数
        /// </summary>
        /// <param name="itemName"></param>
        /// <param name="projectId"></param>
        /// <param name="sections">部门</param>
        /// <param name="sdate"></param>
        /// <param name="edate"></param>
        /// <returns></returns>
        public double GetKpiSafeDay(string itemName, int projectId, List<int> sections, DateTime sdate, DateTime edate)
        {
            var item = from q in _apiDbContext.siger_project_kpi_tasklist
                       join t in _apiDbContext.siger_project_kpi_item on q.ItemId equals t.id
                       where t.Item == itemName && q.projectId == projectId && q.status == (int)RowState.Valid && t.status == (int)RowState.Valid
                             && q.Busidate >= sdate && q.Busidate <= edate && q.Result > KpiTaskResult.NoConfg && q.ActVal > 0
                       select q;
            var task = item.OrderByDescending(q => q.Busidate).FirstOrDefault();
            if (task != null)
            {
                return DateTime.Now.Date.Subtract(task.Busidate.Date).Days;
            }

            return DateTime.Now.DayOfYear;
        }
        /// <summary>
        /// 客户投诉
        /// </summary>
        /// <param name="itemName"></param>
        /// <param name="projectId"></param>
        /// <param name="sections">部门</param>
        /// <param name="sdate"></param>
        /// <param name="edate"></param>
        /// <returns></returns>
        public double GetKpiComplaint(string itemName, int projectId, List<int> sections, DateTime sdate, DateTime edate)
        {
            var item = from q in _apiDbContext.siger_project_kpi_tasklist
                       join t in _apiDbContext.siger_project_kpi_item on q.ItemId equals t.id
                       join s in _apiDbContext.siger_project_section on q.Section equals s.id
                       where t.Item == itemName && q.projectId == projectId && q.status == (int)RowState.Valid && t.status == (int)RowState.Valid
                             && q.Busidate >= sdate && q.Busidate < edate && q.Result > KpiTaskResult.NoConfg && s.title == KpiItemNameKey.SectionTitle && q.ActVal > 0
                       select q;
            var items = item.AsEnumerable();
            if (items.Any())
            {
                return Math.Round(items.Sum(m => m.ActVal), 0);
            }

            return 0;
        }

        /// <summary>
        /// 计算Oee
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="section"></param>
        /// <param name="pid"></param>
        /// <param name="cid"></param>
        /// <returns></returns>
        public ResponseOee GetOee(long start, long end, int section, int pid, int cid)
        {
            var dtStart = UnixTimeHelper.ConvertStringDateTime(start.ToStr());
            var dtEnd = UnixTimeHelper.ConvertStringDateTime(end.ToStr());
            var machines = GetAttributionMachineIds(section, pid);
            var botton_machines = _context.siger_andon_station_config.Where(f => f.project_id == pid && f.status != 0 && f.station_type == 1 && machines.Contains(f.machine_id)).Select(s => s.machine_id);
            var yieldRepository = new LocationYieldRepository(cid, pid);
            var yieldData = yieldRepository.GetLocationYields(botton_machines, dtStart, dtEnd);
            var molecular = 0d;
            var denominator = 0d;
            var products = yieldData.Select(s => s.productName).Distinct();
            foreach (var product in products)
            {
                var working_hours = GetLastProductRouteByName(pid, product);
                var tempYield = yieldData.Where(f => f.productName == product).Sum(s => s.yield);
                molecular += working_hours * tempYield;
            }
            var restTime = 0d;
            var stopExpection = _context.siger_andon_expection_type.FirstOrDefault(f => f.projectid == pid && f.status != 0 && f.is_caloee == 0 && f.is_passprocess == 1)?.id ?? 0;
            if (stopExpection != 0)
            {
                var andonData = _context.siger_andon_info.Where(f => f.projectid == pid && f.status > (int)AndonState.Approve && f.expection_type == stopExpection && f.create_time >= start && f.create_time <= end && botton_machines.Contains(f.machine)).ToList();
                //last data
                var lastData = andonData.FirstOrDefault(f => f.approve_time >= end);
                if (lastData != null)
                {
                    restTime += end - lastData.approve_time;
                }
                //first data
                var firstData = _context.siger_andon_info.Where(f => f.projectid == pid && f.expection_type == stopExpection && f.create_time < start && botton_machines.Contains(f.machine)).OrderByDescending(o => o.create_time).FirstOrDefault();
                if (firstData != null && firstData.complete_time < end && firstData.complete_time > start)
                {
                    andonData.Add(new Middlelayer.TpmRepository.Entities.siger_andon_info { create_time = start, complete_time = firstData.complete_time });
                }
                //sum
                restTime += andonData.Select(s => s.complete_time - s.create_time).Sum();
                //all rest data
                if (firstData != null && firstData.create_time <= start && firstData.complete_time >= end)
                {
                    restTime = end - start;
                }
            }
            denominator = end - start - restTime;
            return new ResponseOee(molecular, denominator);
        }

        private double GetLastProductRouteByName(int pid, string name)
        {
            var ret = 0d;
            var productId = _context.siger_project_product.FirstOrDefault(f => f.name == name && f.projectid == pid && f.status != 0)?.id ?? 0;
            if (productId != 0)
            {
                ret = _context.siger_project_product_route.Where(f => f.productId == productId && f.status != 0 && f.projectId == pid).OrderByDescending(o => o.serialNumber).FirstOrDefault()?.working_hours ?? 0;
            }
            return ret;
        }
    }

}
