using EVCB_OCPP.Packet.Features;
using EVCB_OCPP.Packet.Messages;
using EVCB_OCPP.Packet.Messages.Basic;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using OCPPServer.Protocol;
using System;
using System.Collections.Generic;

namespace EVCB_OCPP.WSServer.Message
{
    /// <summary>
    /// 實現 OCPP16 基本傳送規範，
    /// 1.訊息 基本格式，將訊息包裝成 Call 、CallResult、CallError 三種格式
    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送，一個傳完才能接著送下一個(忽略規則 由Center System定義)
    /// </summary>
    internal class OCPP16MessageHandler
    {
        static protected ILogger logger;

        #region 傳送 or 解析訊息需要欄位
        private const int INDEX_MESSAGEID = 0;
        private const int INDEX_UNIQUEID = 1;
        internal const int TYPENUMBER_CALL = 2;
        private const int INDEX_CALL_ACTION = 2;
        private const int INDEX_CALL_PAYLOAD = 3;

        internal const int TYPENUMBER_CALLRESULT = 3;
        private const int INDEX_CALLRESULT_PAYLOAD = 2;

        internal const int TYPENUMBER_CALLERROR = 4;
        private const int INDEX_CALLERROR_ERRORCODE = 2;
        private const int INDEX_CALLERROR_DESCRIPTION = 3;
        private const int INDEX_CALLERROR_PAYLOAD = 4;

        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
        #endregion

        private List<Profile> profiles = new List<Profile>()
        {
             new CoreProfile(),
             new FirmwareManagementProfile(),
             new ReservationProfile(),
             new RemoteTriggerProfile(),
             new SmartChargingProfile(),
             new LocalAuthListManagementProfile(),
             new SecurityProfile()
        };


        /// <summary>
        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
        /// </summary>
        /// <param name="client"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        internal MessageResult AnalysisReceiveData(ClientData client, string data)
        {
            MessageResult result = new MessageResult();
            try
            {

                var msg = Parse(data);
                if (msg != null)
                {

                    result.UUID = msg.Id;
                    switch (msg.TypeId)
                    {
                        case TYPENUMBER_CALL:
                            {
                                //只有CallMessage 才有在RawData有Action
                                BasicMessageResult baseResult = UnPackPayloadbyCall(msg.Action, msg.Payload.ToString());
                                Actions action = Actions.None;
                                Enum.TryParse<Actions>(msg.Action, out action);
                                result.Action = msg.Action;
                                if (baseResult.Request != null)
                                {
                                    if (baseResult.Request.Validate())
                                    {
                                        result.Id = TYPENUMBER_CALL;
                                        result.Message = baseResult.Request;

                                    }
                                    else
                                    {

                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
                                             OCPPErrorDescription.OccurenceConstraintViolation);
                                        result.Id = TYPENUMBER_CALL;
                                        result.Message = baseResult.Request;
                                        result.Success = false;
                                        result.CallErrorMsg = replyMsg;
                                        result.Exception = new Exception("Validation Failed");

                                    }
                                }
                                else
                                {

                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation, OCPPErrorDescription.OccurenceConstraintViolation);
                                    result.Id = TYPENUMBER_CALL;
                                    result.Message = baseResult.Request;
                                    result.Success = false;
                                    result.CallErrorMsg = replyMsg;
                                    result.Exception = baseResult.Exception;
                                }

                            }
                            break;
                        case TYPENUMBER_CALLRESULT:
                            {
                                BasicMessageResult baseResult = UnPackPayloadbyCallResult(client.queue, msg.Id, msg.Payload.ToString());

                                if (baseResult.Confirmation != null)
                                {

                                    if (baseResult.Confirmation.Validate())
                                    {
                                        result.Id = TYPENUMBER_CALLRESULT;
                                        result.Message = baseResult.Confirmation;
                                        result.Action = baseResult.Confirmation.GetRequest().Action;
                                        //return data
                                    }
                                    else
                                    {
                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
                                      OCPPErrorDescription.OccurenceConstraintViolation);
                                        result.Id = TYPENUMBER_CALLRESULT;
                                        result.Message = baseResult.Confirmation;
                                        result.Success = false;
                                        result.CallErrorMsg = replyMsg;
                                        result.Exception = new Exception("Validate Failed");
                                    }
                                }
                                else
                                {
                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
                                   OCPPErrorDescription.OccurenceConstraintViolation);
                                    result.Id = TYPENUMBER_CALLRESULT;
                                    result.Message = baseResult.Confirmation;
                                    result.Success = false;
                                    result.CallErrorMsg = replyMsg;
                                    result.Exception = baseResult.Exception;
                                }

                            }
                            break;
                        case TYPENUMBER_CALLERROR:
                            {
                                result.Id = TYPENUMBER_CALLERROR;
                                var sentRequest = UnPackPayloadbyCallError(client.queue, msg.Id);

                                if (sentRequest != null)
                                {
                                    IRequest request = sentRequest as IRequest;
                                    result.Action = request.Action;

                                    result.Message = sentRequest;
                                    result.ReceivedErrorCode = string.Format("ErrorMsg {0}:{1}", ((CallErrorMessage)msg).ErrorCode, ((CallErrorMessage)msg).ErrorDescription);
                                }


                            }
                            break;
                        default:
                            break;

                    }

                    // if (msg != null) Console.WriteLine(string.Format("Receieved Message : {0}", msg.ToString()));

                }
            }
            catch (Exception ex)
            {
                if (string.IsNullOrEmpty(result.UUID))
                {
                    result.UUID = data.Substring(4, 39);
                    result.UUID = result.UUID.Split(new string[] { "\"," }, StringSplitOptions.None)[0];
                }
                result.Success = false;
                result.Exception = ex;
            }
            return result;
        }

        #region 解析收到的訊息
        /// <summary>
        /// Parse data to OCPP Basic Message
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private BaseMessage Parse(string message)
        {
            try
            {
                if (message.StartsWith("[4,\""))
                {
                    message = message.Replace('{', '"');
                    message = message.Replace('}', '"');
                }
                var array = JsonConvert.DeserializeObject<JArray>(message);
                BaseMessage msg = null;
                switch ((int)array[INDEX_MESSAGEID])
                {
                    case TYPENUMBER_CALL:
                        {
                            CallMessage call = new CallMessage();
                            call.Action = array[INDEX_CALL_ACTION].ToString();
                            call.Payload = array[INDEX_CALL_PAYLOAD].ToString().Replace("\r\n", "");
                            msg = call;
                        }
                        break;
                    case TYPENUMBER_CALLRESULT:
                        {
                            CallResultMessage callResult = new CallResultMessage();
                            callResult.Payload = array[INDEX_CALLRESULT_PAYLOAD].ToString().Replace("\r\n", "");
                            msg = callResult;
                        }
                        break;
                    case TYPENUMBER_CALLERROR:
                        {
                            CallErrorMessage callError = new CallErrorMessage();
                            callError.ErrorCode = array[INDEX_CALLERROR_ERRORCODE].ToString();
                            callError.ErrorDescription = array[INDEX_CALLERROR_DESCRIPTION].ToString();
                            callError.ErrorDetails = array[INDEX_CALLERROR_PAYLOAD].ToString().Replace("\r\n", "");
                            msg = callError;
                        }
                        break;
                    default:
                        throw new Exception("Message Type notSupported");


                }
                msg.Id = array[INDEX_UNIQUEID].ToString();
                return msg;
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Parse Error=>  Problem: {0}", ex.Message));

            }



        }


        private BasicMessageResult UnPackPayloadbyCall(string action, string payload)
        {
            BasicMessageResult result = new BasicMessageResult();
            try
            {
                Feature feature = null;
                foreach (var profile in profiles)
                {
                    feature = profile.GetFeaturebyAction(action);
                    if (feature == null)
                    {
                        continue;
                    }
                    else
                    {
                        break;
                    }
                }

                result.Request = JsonConvert.DeserializeObject(payload, feature.GetRequestType()) as IRequest;

            }
            catch (Exception ex)
            {
                result.Exception = ex;
                logger.LogError(string.Format("[{0}]UnPackPayloadbyCall Ex: {1}", action, ex.Message), "UnPack");

            }

            return result;
        }

        private BasicMessageResult UnPackPayloadbyCallResult(Queue requestQueue, string uniqueId, string payload)
        {
            int i = 1;
            BasicMessageResult result = new BasicMessageResult();
            try
            {
                i = -1 ;
                IRequest request = requestQueue.RestoreRequest(uniqueId);

                if (request == null)
                {
                    result.Exception = new Exception("Can't find request");
                    return result;
                }
                i = 2;
                Feature feature = null;
                foreach (var profile in profiles)
                {
                    i = 3;
                    feature = profile.GetFeaturebyType(request.GetType());
                    i = 4;
                    if (feature == null)
                    {
                        continue;
                    }
                    else
                    {
                        break;
                    }
                }
                i = 5;
                IConfirmation confrim = JsonConvert.DeserializeObject(payload, feature.GetConfirmationType()) as IConfirmation;
                i = 6;
                confrim.SetRequest(request);
                i = 7;
                result.Confirmation = confrim;

            }
            catch (Exception ex)
            {
                result.Exception = ex;
                logger.LogError(string.Format(i+"UnPackPayloadbyCallResult Data:[{0},{1}] Ex: {2}", uniqueId, payload, ex.ToString()), "UnPack");

            }
            return result;

        }

        private IRequest UnPackPayloadbyCallError(Queue requestQueue, string uniqueId)
        {
            IRequest sentMsg = requestQueue.RestoreRequest(uniqueId);

            return sentMsg;

        }
        #endregion


    }
}

