using EVCB_OCPP.Domain;
using EVCB_OCPP.Domain.Models.MainDb;
using EVCB_OCPP.Packet.Features;
using EVCB_OCPP.Packet.Messages;
using EVCB_OCPP.Packet.Messages.Core;
using EVCB_OCPP.WSServer.Message;
using EVCB_OCPP.WSServer.Service;
using EVCB_OCPP.WSServer.Service.WsService;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.VisualBasic;
using Newtonsoft.Json;
using Quartz;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EVCB_OCPP.WSServer.Jobs;

[DisallowConcurrentExecution]
public class ServerMessageJob : IJob
{
    public ServerMessageJob(
        ProtalServer protalServer,
        ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice,
        //VendorIdUpdateService vendorIdReplaceService,
        IConfiguration configuration,
        IDbContextFactory<MainDBContext> maindbContextFactory,
        ILogger<ServerMessageJob> logger)
    {
        this.protalServer = protalServer;
        this.confirmWaitingMessageSerevice = confirmWaitingMessageSerevice;
        //this.vendorIdUpdateService = vendorIdReplaceService;
        this.maindbContextFactory = maindbContextFactory;
        this.logger = logger;
    }

    private readonly ProtalServer protalServer;
    private readonly ConfirmWaitingMessageSerevice confirmWaitingMessageSerevice;
    //private readonly VendorIdUpdateService vendorIdUpdateService;
    private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
    private readonly ILogger<ServerMessageJob> logger;

    public async Task Execute(IJobExecutionContext context)
    {
        //logger.LogDebug("{0} Started", nameof(ServerMessageJob));
        try
        {
            await ExecuteTrigger();
        }
        catch (Exception ex)
        {
            logger.LogError("ServerMessageTrigger  Ex:{0}", ex.ToString());
        }
    }

    private Task ExecuteTrigger()
    {
        var clientDic = protalServer.GetClientDic();

        confirmWaitingMessageSerevice.RemoveExpiredConfirmMessage();
        ResendServerMessage(clientDic);
        return SendNewServerMessage(clientDic);
    }

    private async Task SendNewServerMessage(Dictionary<string, WsClientData> clientDic)
    {
        BasicMessageHandler msgAnalyser = new BasicMessageHandler();
        DateTime dateTimeNow = DateTime.UtcNow;
        DateTime startDt = dateTimeNow.AddSeconds(-30);
        DateTime dt = new DateTime(1991, 1, 1);
        DateTime currentTime = dateTimeNow;
        List<ServerMessage> commandList;
        List<Task> waitTasks = new List<Task>();

        using (var db = await maindbContextFactory.CreateDbContextAsync())
        {
            commandList = await db.ServerMessage.Where(c => c.ReceivedOn == dt && c.UpdatedOn == dt && c.CreatedOn >= startDt && c.CreatedOn <= currentTime).AsNoTracking().ToListAsync();
        }
        //處理主機傳送的有指令
        var cmdMachineList = commandList.Select(c => c.ChargeBoxId).Distinct().ToList();
        if (commandList.Count > 0)
        {
            // Console.WriteLine(string.Format("Now:{0} commandList Count:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), commandList.Count));
        }

        Dictionary<WsClientData, List<ServerMessage>> toSendWsClientDataMsgPair = 
            commandList
                .GroupBy(x => x.ChargeBoxId)
                .Where(x => clientDic.ContainsKey(x.Key))
                .ToDictionary(x => clientDic[x.Key], x => x.ToList());

        await Parallel.ForEachAsync(toSendWsClientDataMsgPair, async (sendPair, token) => //{  });
        //foreach (var charger_SN in cmdMachineList)
        {
            WsClientData session = sendPair.Key;
            string charger_SN = session.ChargeBoxId;
            string uuid = string.Empty;

            //logger.LogDebug(string.Format("charger_SN:{0} startDt:{1} CreatedOn:{2}", charger_SN, startDt.ToString("yyyy/MM/dd HH:mm:ss"), DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss")));

            if (session.ISOCPP20)
            {
                //continue;
                return;
            }

            string rawRequest = string.Empty;

            List<ServerMessage> cmdList = sendPair.Value;

            var profiles = protalServer.Profiles;

            foreach (var item in cmdList)
            {
                IRequest request = null;
                Actions action = Actions.None;
                Enum.TryParse(item.OutAction, out action);
                Type _RequestType = null;

                for (int i = 0; i < profiles.Count; i++)
                {
                    var feature = profiles[i].GetFeaturebyAction(item.OutAction);

                    if (feature != null)
                    {
                        _RequestType = feature.GetRequestType();
                        break;
                    }
                }

                if (_RequestType != null && item.CreatedBy != "Destroyer")
                {
                    request = JsonConvert.DeserializeObject(item.OutRequest, _RequestType) as IRequest;
                    if (action == Actions.DataTransfer)
                    {
                        ReplaceVID(session, action, request);
                    }
                    uuid = session.queue.store(request);
                    rawRequest = BasicMessageHandler.GenerateRequest(uuid, item.OutAction, request);
                    protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");
                }


                if (item.CreatedBy == "Destroyer")
                {

                    if (_RequestType != null)
                    {
                        request = Activator.CreateInstance(_RequestType) as IRequest;
                        uuid = session.queue.store(request);
                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(uuid, item.OutAction, item.OutRequest);
                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");

                    }
                    else
                    {

                        rawRequest = BasicMessageHandler.GenerateDestroyRequest(Guid.NewGuid().ToString(), item.OutAction, item.OutRequest);
                        protalServer.SendMsg(session, rawRequest, string.Format("{0} {1}", action, "Request"), "");

                    }
                }

                var tmpTask = confirmWaitingMessageSerevice.Add(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);
                waitTasks.Add(tmpTask);
                //protalServer.AddConfirmMessage(charger_SN, item.Id, item.SerialNo, item.OutAction, uuid, item.CreatedBy, rawRequest);

                await Task.Delay(100);

            }
        }
        );
        await Task.WhenAll(waitTasks);
    }

    private void ResendServerMessage(Dictionary<string, WsClientData> clientDic)
    {
        DateTime dateTimeNow = DateTime.UtcNow;
        List<NeedConfirmMessage> resendList = confirmWaitingMessageSerevice.GetPendingMessages();
        foreach (var resendItem in resendList)
        {
            WsClientData session;
            if (clientDic.TryGetValue(resendItem.ChargePointSerialNumber, out session))
            {
                if (dateTimeNow.Subtract(resendItem.SentOn).TotalSeconds > 1)
                {
                    confirmWaitingMessageSerevice.SignalMessageSended(resendItem);
                    protalServer.SendMsg(session, resendItem.SentMessage, string.Format("{0} {1}", resendItem.SentAction, "Request"), "");
                }

            }

        }
    }

    private void ReplaceVID(WsClientData session, Actions action, IRequest request)
    {
        if (action != Actions.DataTransfer ||
            request is not DataTransferRequest dataTransferRequest)
        {
            return;
        }

        string vid = session.ChargePointVendor;
        if (string.IsNullOrEmpty(vid))
        {
            return;
        }
        dataTransferRequest.vendorId = vid;
    }
}