
using EVCB_OCPP.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MongoDB.Driver.Core.Servers;
using OCPPPackage.Profiles;
using SuperWebSocket;
using SuperWebSocket.SubProtocol;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace OCPPServer.Protocol
{
    public class OCPPWSServer : WebSocketServer<ClientData>
    {

        static private ILogger logger;
        private readonly IConfiguration configuration;
        private readonly IServiceProvider serviceProvider;

        /// <summary>
        /// 可允許連線Clinet數
        /// </summary>
        public int connectNum { get; set; }

        /// <summary>
        /// 是否限制連線Clinet數
        /// </summary>
        public bool beConnectLimit { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
        /// </summary>
        /// <param name="subProtocols">The sub protocols.</param>
        public OCPPWSServer(IEnumerable<ISubProtocol<ClientData>> subProtocols, IServiceProvider serviceProvider)
            : base(subProtocols)
        {
            this.configuration = serviceProvider.GetService<IConfiguration>();
            this.serviceProvider = serviceProvider;

            logger = serviceProvider.GetService<ILogger<OCPPWSServer>>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
        /// </summary>
        /// <param name="subProtocol">The sub protocol.</param>
        public OCPPWSServer(ISubProtocol<ClientData> subProtocol, IServiceProvider serviceProvider)
            : base(subProtocol)
        {
            this.configuration = serviceProvider.GetService<IConfiguration>();
            this.serviceProvider = serviceProvider;

            logger = serviceProvider.GetService<ILogger<OCPPWSServer>>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
        /// </summary>
        public OCPPWSServer(IServiceProvider serviceProvider)
            : base(new List<ISubProtocol<ClientData>>())
        {
            this.configuration = serviceProvider.GetService<IConfiguration>();
            this.serviceProvider = serviceProvider;

            logger = serviceProvider.GetService<ILogger<OCPPWSServer>>();
        }

        protected override bool ValidateClientCertificate(ClientData session, object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            //  Console.WriteLine(string.Format("{0} :{1}", session.ChargeBoxId + " ValidateClientCertificate", sslPolicyErrors));
            return true;
            // return base.ValidateClientCertificate(session, sender, certificate, chain, sslPolicyErrors);
        }

        protected override bool ValidateHandshake(ClientData session, string origin)
        {
            session.ISOCPP20 = session.SecWebSocketProtocol.ToLower().Contains("ocpp2.0");

            int securityProfile = 0;
            string authorizationKey = string.Empty;
            if (string.IsNullOrEmpty(session.Path))
            {
                //logger.Log();
                logger.LogWarning("===========================================");
                logger.LogWarning("session.Path EMPTY");
                logger.LogWarning("===========================================");
            }

            string[] words = session.Path.Split('/');
            session.ChargeBoxId = words.Last();

            if (configuration["MaintainMode"] == "1")
            {
                session.ChargeBoxId = session.ChargeBoxId + "_2";
            }

            logger.LogInformation(string.Format("ValidateHandshake: {0}", session.Path));
            bool isExistedSN = false;
            bool authorizated = false;
            using (var db = serviceProvider.GetService<IDbContextFactory<MainDBContext>>().CreateDbContext())
            {
                var machine = db.Machine.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefault();
                session.CustomerName = machine == null ? "Unknown" : db.Customer.Where(x => x.Id == machine.CustomerId).Select(x => x.Name).FirstOrDefault();
                session.CustomerId = machine == null ? Guid.Empty : machine.CustomerId;
                session.MachineId = machine == null ? String.Empty : machine.Id;
                isExistedSN = machine == null ? false : true;

                if (!isExistedSN)
                {
                    StringBuilder responseBuilder = new StringBuilder();

                    responseBuilder.AppendFormatWithCrCf(@"HTTP/{0} {1} {2}", "1.1",
                    (int)HttpStatusCode.NotFound, @"Not Found");
                  
                    responseBuilder.AppendWithCrCf();
                    string sb = responseBuilder.ToString();
                    byte[] data = Encoding.UTF8.GetBytes(sb);
                    ((IWebSocketSession)session).SendRawData(data, 0, data.Length);

                    logger.LogInformation(sb);
                    return false;
                }

                var configVaule = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.SecurityProfile)
                                  .Select(x => x.ConfigureSetting).FirstOrDefault();
                int.TryParse(configVaule, out securityProfile);

                if (session.ISOCPP20)
                {
                    // 1.6 server only support change server  function
                    securityProfile = 0;
                }
            }
            if (securityProfile == 3 && session.UriScheme == "ws")
            {
                StringBuilder responseBuilder = new StringBuilder();

                responseBuilder.AppendFormatWithCrCf(@"HTTP/{0} {1} {2}", "1.1",
                (int)HttpStatusCode.Unauthorized, @"Unauthorized");

                responseBuilder.AppendWithCrCf();
                string sb = responseBuilder.ToString();
                byte[] data = Encoding.UTF8.GetBytes(sb);

                ((IWebSocketSession)session).SendRawData(data, 0, data.Length);
                logger.LogInformation(sb);
                return false;
            }

            if ((securityProfile == 1 || securityProfile == 2))
            {
                if (securityProfile == 2 && session.UriScheme == "ws")
                {
                    authorizated = false;
                }

                if (session.Items.ContainsKey("Authorization") || session.Items.ContainsKey("authorization"))
                {

                    using (var db = serviceProvider.GetService<IDbContextFactory<MainDBContext>>().CreateDbContext())
                    {

                        authorizationKey = db.MachineConfigurations.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.ConfigureName == StandardConfiguration.AuthorizationKey)
                                          .Select(x => x.ConfigureSetting).FirstOrDefault();


                        if (session.ISOCPP20)
                        {
                            // 1.6 server only support change server  function
                            securityProfile = 0;
                        }
                    }
                    logger.LogInformation("***********Authorization   ");

                    if (!string.IsNullOrEmpty(authorizationKey))
                    {
                        string base64Encoded = session.Items.ContainsKey("Authorization") ? session.Items["Authorization"].ToString().Replace("Basic ", "") : session.Items["authorization"].ToString().Replace("Basic ", "");
                        byte[] data = Convert.FromBase64String(base64Encoded);
                        string[] base64Decoded = System.Text.ASCIIEncoding.ASCII.GetString(data).Split(':');
                        logger.LogInformation("***********Authorization   " + System.Text.ASCIIEncoding.ASCII.GetString(data));
                        if (base64Decoded.Count() == 2 && base64Decoded[0] == session.ChargeBoxId && base64Decoded[1] == authorizationKey)
                        {
                            authorizated = true;
                        }
                    }





                }
                else
                {
                    authorizated = true;

                }



                if (!authorizated)
                {
                    StringBuilder responseBuilder = new StringBuilder();

                    responseBuilder.AppendFormatWithCrCf(@"HTTP/{0} {1} {2}", "1.1",
                    (int)HttpStatusCode.Unauthorized, @"Unauthorized");

                    responseBuilder.AppendWithCrCf();
                    string sb = responseBuilder.ToString();
                    byte[] data = Encoding.UTF8.GetBytes(sb);

                    ((IWebSocketSession)session).SendRawData(data, 0, data.Length);
                    logger.LogInformation(sb);
                    return false;
                }
            }


         



            return true;
        }
    }
}