using Backend.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Quartz;
using Repository.BackendRepository.Interface;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace BackendWorkerService.Quartz.Jobs
{
    /// <summary>
    /// 電錶歸檔,每小時執行,只執行小時歸檔
    /// </summary>
    [DisallowConcurrentExecution]
    class ArchiveElectricMeterHourJob : IJob
    {
        private readonly ILogger<ArchiveElectricMeterHourJob> logger;
        private readonly IBackgroundServiceRepository backgroundServiceRepository;
        private readonly ILogger<Task_Detail> loggers;

        public ArchiveElectricMeterHourJob(
            ILogger<ArchiveElectricMeterHourJob> logger,
            IBackgroundServiceRepository backgroundServiceRepository, ILogger<Task_Detail> loggers)
        {
            this.logger = logger;
            this.backgroundServiceRepository = backgroundServiceRepository;
            this.loggers = loggers;
        }

        public async Task Execute(IJobExecutionContext context)
        {
            Task_Detail task_Detail = new Task_Detail(loggers, backgroundServiceRepository);
            try
            {
                if(await task_Detail.GetNeedWorkTask("ArchiveElectricMeterHourJob", "All"))
                {
                    await task_Detail.InsertWorkTime("ArchiveElectricMeterHourJob", "All", "任務開始");
                    EDFunction ed = new EDFunction();
                    XmlDocument xmlDocument = new XmlDocument();

                    var sqlArchive = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'archiveConfig'";

                    var variableArchive = await backgroundServiceRepository.GetAllAsync<KeyValue>(sqlArchive);
                    var electricMeterGuid = variableArchive.Where(x => x.Name == "ElectricMeterGuid").Select(x => x.Value).FirstOrDefault();

                    #region 找出所有電錶設備
                    var sWhere = "deleted = 0 AND sub_system_guid = @sub_system_guid";
                    var electricMeters = await backgroundServiceRepository.GetAllAsync<Device>("device", sWhere, new { sub_system_guid = electricMeterGuid });
                    #endregion 找出所有電錶設備

                    #region 找出所有電錶系統的點位
                    var sPointWhere = "deleted = 0 AND sub_system_guid = @sub_system_guid";
                    var points = await backgroundServiceRepository.GetAllAsync<Device_item>("device_item", sPointWhere, new { sub_system_guid = electricMeterGuid });
                    #endregion 找出所有電錶系統的點位

                    #region 組合出所有電錶設備點位
                    List<DeviceNumberPoint> deviceNumberPoints = new List<DeviceNumberPoint>();
                    foreach (var electricMeter in electricMeters)
                    {
                        foreach (var point in points)
                        {
                            DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint();
                            deviceNumberPoint.DeviceNumber = electricMeter.Device_number;
                            deviceNumberPoint.Point = point.points;
                            deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", electricMeter.Device_number, point.points);

                            deviceNumberPoints.Add(deviceNumberPoint);
                        }
                    }
                    #endregion 組合出所有電錶設備點位

                    #region 取得obix 設定
                    var obixApiConfig = new ObixApiConfig();

                    var sqlObix = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'obixConfig'";

                    var variableObix = await backgroundServiceRepository.GetAllAsync<KeyValue>(sqlObix);
                    obixApiConfig.ApiBase = variableObix.Where(x => x.Name == "ApiBase").Select(x => x.Value).FirstOrDefault();
                    obixApiConfig.UserName = ed.AESDecrypt(variableObix.Where(x => x.Name == "UserName").Select(x => x.Value).FirstOrDefault());
                    obixApiConfig.Password = ed.AESDecrypt(variableObix.Where(x => x.Name == "Password").Select(x => x.Value).FirstOrDefault());

                    String encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(obixApiConfig.UserName + ":" + obixApiConfig.Password));
                    #endregion 取得obix 設定

                    var now = DateTime.Now;

                    #region 小時歸檔
                    try
                    {
                        var preHour = now.AddHours(-1); //取得前一小時

                        var tempHour = string.Empty;
                        if (preHour.Hour < 10)
                        {
                            tempHour = "0" + preHour.Hour.ToString();
                        }
                        else
                        {
                            tempHour = preHour.Hour.ToString();
                        }

                        var startTimestamp = string.Format("{0}T{1}:00:00.000+08:00", preHour.ToString("yyyy-MM-dd"), tempHour);
                        var endTimestamp = string.Format("{0}T{1}:59:59.000+08:00", preHour.ToString("yyyy-MM-dd"), tempHour);

                        var historyQueryFilter = $@"<obj is='obix: HistoryFilter'>
                                                    <abstime name='start' val='{startTimestamp}' />
                                                    <abstime name='end' val='{endTimestamp}' />
                                                    <reltime name='interval' val = 'PT1H' />
                                                </obj>";

                        //Stopwatch stopWatch = new Stopwatch();
                        //stopWatch.Start();

                        //抓取每個設備的資料
                        List<Dictionary<string, object>> archiveHourRawDatas = new List<Dictionary<string, object>>();
                        foreach (var deviceNumberPoint in deviceNumberPoints)
                        {

                            HttpWebRequest archiveHourRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/");
                            //HttpWebRequest archiveHourRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/");
                            archiveHourRequest.Method = "POST";
                            archiveHourRequest.Headers.Add("Authorization", "Basic " + encoded);
                            archiveHourRequest.PreAuthenticate = true;

                            byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter);
                            using (Stream reqStream = archiveHourRequest.GetRequestStream())
                            {
                                reqStream.Write(byteArray, 0, byteArray.Length);
                            }

                            HttpWebResponse archiveHourResponse = (HttpWebResponse)archiveHourRequest.GetResponse();
                            var archiveHourResponseContent = new StreamReader(archiveHourResponse.GetResponseStream()).ReadToEnd();

                            xmlDocument.LoadXml(archiveHourResponseContent);
                            string archiveHourJson = JsonConvert.SerializeXmlNode(xmlDocument);
                            JObject archiveHourJsonResult = (JObject)JsonConvert.DeserializeObject(archiveHourJson);

                            if (archiveHourJsonResult.ContainsKey("err")) //抓取錯誤
                            {
                                //logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【取得資料失敗】");
                                //logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveHourJsonResult);

                                Dictionary<string, object> archiveHourRawData = new Dictionary<string, object>();
                                archiveHourRawData.Add("@device_number", deviceNumberPoint.DeviceNumber);
                                archiveHourRawData.Add("@point", deviceNumberPoint.Point);
                                archiveHourRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19));
                                archiveHourRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19));
                                archiveHourRawData.Add("@is_complete", 0);
                                archiveHourRawData.Add("@repeat_times", 0);
                                archiveHourRawData.Add("@fail_reason", archiveHourJson);

                                archiveHourRawDatas.Add(archiveHourRawData);
                            }

                            if (archiveHourJsonResult.ContainsKey("obj")) //表示可以讀取到內容
                            {
                                var histories = archiveHourJsonResult["obj"]["list"];
                                var rawdateCount = Convert.ToInt32(archiveHourJsonResult["obj"]["int"]["@val"].ToString());
                                if (histories != null && histories.HasValues)
                                {
                                    if (rawdateCount > 1)
                                    {   //多筆資料
                                        foreach (var history in histories)
                                        {
                                            Dictionary<string, object> archiveHourRawData = new Dictionary<string, object>();
                                            archiveHourRawData.Add("@device_number", deviceNumberPoint.DeviceNumber);
                                            archiveHourRawData.Add("@point", deviceNumberPoint.Point);

                                            //時間
                                            if (history["abstime"] != null && history["abstime"].HasValues)
                                            {
                                                foreach (var abstime in history["abstime"])
                                                {
                                                    var name = abstime["@name"].ToString();
                                                    switch (name)
                                                    {
                                                        case "start":
                                                            var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss");
                                                            archiveHourRawData.Add("@start_timestamp", startTimstamp);
                                                            break;
                                                        case "end":
                                                            var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss");
                                                            archiveHourRawData.Add("@end_timestamp", endTimstamp);
                                                            break;
                                                    }
                                                }
                                            }

                                            //區間內資料筆數
                                            if (history["int"] != null && history["int"].HasValues)
                                            {
                                                var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString());
                                                archiveHourRawData.Add("@count_rawdata", count);
                                            }

                                            //整合數值(最大、最小、平均、總和)
                                            if (history["real"] != null && history["real"].HasValues)
                                            {
                                                foreach (var real in history["real"])
                                                {
                                                    var name = real["@name"].ToString();
                                                    switch (name)
                                                    {
                                                        case "min":
                                                            var min = Convert.ToDecimal(real["@val"].ToString());
                                                            archiveHourRawData.Add("@min_rawdata", min);
                                                            break;
                                                        case "max":
                                                            var max = Convert.ToDecimal(real["@val"].ToString());
                                                            archiveHourRawData.Add("@max_rawdata", max);
                                                            break;
                                                        case "avg":
                                                            var avg = Convert.ToDecimal(real["@val"].ToString());
                                                            archiveHourRawData.Add("@avg_rawdata", avg);
                                                            break;
                                                        case "sum":
                                                            var sum = Convert.ToDecimal(real["@val"].ToString());
                                                            archiveHourRawData.Add("@sum_rawdata", sum);
                                                            break;
                                                    }
                                                }
                                            }
                                            archiveHourRawData.Add("@is_complete", 1);

                                            archiveHourRawDatas.Add(archiveHourRawData);
                                        }
                                    }
                                    else
                                    {   //單筆資料
                                        Dictionary<string, object> archiveHourRawData = new Dictionary<string, object>();
                                        archiveHourRawData.Add("@device_number", deviceNumberPoint.DeviceNumber);
                                        archiveHourRawData.Add("@point", deviceNumberPoint.Point);

                                        //時間
                                        if (histories["obj"]["abstime"] != null && histories["obj"]["abstime"].HasValues)
                                        {
                                            foreach (var abstime in histories["obj"]["abstime"])
                                            {
                                                var name = abstime["@name"].ToString();
                                                switch (name)
                                                {
                                                    case "start":
                                                        var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss");
                                                        archiveHourRawData.Add("@start_timestamp", startTimstamp);
                                                        break;
                                                    case "end":
                                                        var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss");
                                                        archiveHourRawData.Add("@end_timestamp", endTimstamp);
                                                        break;
                                                }
                                            }
                                        }

                                        //區間內資料筆數
                                        if (histories["obj"]["int"] != null && histories["obj"]["int"].HasValues)
                                        {
                                            var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString());
                                            archiveHourRawData.Add("@count_rawdata", count);
                                        }

                                        //整合數值(最大、最小、平均、總和)
                                        if (histories["obj"]["real"] != null && histories["obj"]["real"].HasValues)
                                        {
                                            foreach (var real in histories["obj"]["real"])
                                            {
                                                var name = real["@name"].ToString();
                                                switch (name)
                                                {
                                                    case "min":
                                                        var min = Convert.ToDecimal(real["@val"].ToString());
                                                        archiveHourRawData.Add("@min_rawdata", min);
                                                        break;
                                                    case "max":
                                                        var max = Convert.ToDecimal(real["@val"].ToString());
                                                        archiveHourRawData.Add("@max_rawdata", max);
                                                        break;
                                                    case "avg":
                                                        var avg = Convert.ToDecimal(real["@val"].ToString());
                                                        archiveHourRawData.Add("@avg_rawdata", avg);
                                                        break;
                                                    case "sum":
                                                        var sum = Convert.ToDecimal(real["@val"].ToString());
                                                        archiveHourRawData.Add("@sum_rawdata", sum);
                                                        break;
                                                }
                                            }
                                        }
                                        archiveHourRawData.Add("@is_complete", 1);

                                        archiveHourRawDatas.Add(archiveHourRawData);
                                    }
                                }
                            }
                        }

                        //stopWatch.Stop();
                        //logger.LogInformation("【ArchiveElectricMeterHourJob】【小時歸檔】【效能檢驗】[取得資料花費時間]{0} 毫秒", stopWatch.ElapsedMilliseconds);

                        if (archiveHourRawDatas.Count() > 0)
                        {
                            await backgroundServiceRepository.AddMutiByCustomTable(archiveHourRawDatas, "archive_electric_meter_hour");
                        }

                        
                        await task_Detail.InsertWorkTime_End("ArchiveElectricMeterHourJob", "All", "任務完成");
                    }
                    catch (Exception exception)
                    {
                        await task_Detail.WorkFail("ArchiveElectricMeterHourJob", "All", exception.ToString());
                        logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【任務失敗】");
                        logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【任務失敗】[Exception]:{0}", exception.ToString());
                    }
                    #endregion 小時歸檔


                }

            }
            catch (Exception exception)
            {
                await task_Detail.WorkFail("ArchiveElectricMeterHourJob", "All", exception.ToString());
                logger.LogError("【ArchiveElectricMeterHourJob】【任務失敗】");
                logger.LogError("【ArchiveElectricMeterHourJob】【任務失敗】[Exception]:{0}", exception.ToString());
            }
        }
    }
}