﻿using AsmodeeDigital.Common.Plugin.Domain.Data;
using AsmodeeDigital.Common.Plugin.Manager.Event;
using AsmodeeDigital.Common.Plugin.Utils;
using BestHTTP;
using System;
using System.Collections;
using System.Text;
using UnityEngine;

namespace AsmodeeDigital.Common.Plugin.Network
{
    public partial class RestAPI
    {
        #region Events
        public event Action<AuthenticationTokens> ConnectionSuccessEvent;

        public event Action<DataUser> EmailFoundEvent;

        public event Action EmailNotFoundEvent;

        public event Action<DataUser> LoginFoundEvent;

        public event Action LoginNotFoundEvent;

        public event Action<AuthenticationTokens> AuthenticationSuccessEvent;

        public event Action<string> AuthenticationErrorEvent;

        public event Action<DataUser> AccountCreatedEvent;

        public event Action<string> AccountCreationErrorEvent;

        public event Action LostPasswordEmailSentEvent;

        public event Action<string> LostPasswordErrorEvent;

        public event Action<string> ErrorEvent;

        public event Action RequestTimedOutEvent;
        #endregion

        /// <summary>
        /// Set to True for Unit Test.
        /// </summary>
        public bool IsInTestMode = false;

        /// <summary>
        /// Connection parameters to DoW server
        /// </summary>
        private DoWNetworkParameters _networkParameters;

        /// <summary>
        /// Custom X509 certificate verifier
        /// </summary>
        private CustomVerifier _customVerifyer;

        /// <summary>
        /// OAuth2 access token and refresh token
        /// </summary>
        private AuthenticationTokens _authenticationTokens;

        private string _platform;
        private string _language;

        public RestAPI(DoWNetworkParameters networkParameters)
        {
            _networkParameters = networkParameters;

            _customVerifyer = new CustomVerifier(networkParameters);

            Init();
        }

        private void Init()
        {
            RuntimePlatform platform = Application.platform;

            if (platform == RuntimePlatform.IPhonePlayer)
            {
                _platform = "appstore";
            }
            else if (platform == RuntimePlatform.Android)
            {
                _platform = "googleplay";
            }
            // TODO : What if DRM Free ? Humble Store
            else
            {
                _platform = "steam";
            }

            SetLanguage(Application.systemLanguage);

            Debug.LogFormat("Platform : {0}", _platform);
            Debug.LogFormat("Language : {0}", _language);
        }

        public void SetLanguage(SystemLanguage language)
        {
            _language = LanguageHelper.Get2LetterISOCodeFromSystemLanguage(language);
        }

        /// <summary>
        /// Create HTTP User-Agent for REST Requests
        /// </summary>
        /// <returns></returns>
        private string GetUserAgent()
        {
            return String.Format("{0}/{1} {2}/ Unity/{3}", Application.productName, Application.version, Application.platform, Application.unityVersion);
        }

        private HTTPRequest RequestCallGet(string accessToken, string endpoint, Action<Data> dataResponse, Action<DataError> error = null)
        {
            return RequestCall(accessToken, endpoint, true, null, HTTPMethods.Get, dataResponse, error);
        }

        private HTTPRequest RequestCallPost(string accessToken, string endpoint, bool transformParametersToJson, Hashtable parameters, Action<string> dataResponse, Action<DataError> error = null)
        {
            return RequestCall(accessToken, endpoint, transformParametersToJson, parameters, HTTPMethods.Post, dataResponse, error);
        }

        private HTTPRequest RequestCallPost(string accessToken, string endpoint, bool transformParametersToJson, Hashtable parameters, Action<Data> dataResponse, Action<DataError> error = null)
        {
            return RequestCall(accessToken, endpoint, transformParametersToJson, parameters, HTTPMethods.Post, dataResponse, error);
        }

        private HTTPRequest RequestCallDelete(string accessToken, string endpoint, Action<Data> dataResponse, Action<DataError> error = null)
        {
            return RequestCall(accessToken, endpoint, false, null, HTTPMethods.Delete, dataResponse, error);
        }

        private HTTPRequest RequestCall(string accessToken, string endpoint, bool transformParametersToJson, Hashtable parameters, BestHTTP.HTTPMethods httpMethod, Action<string> dataResponse, Action<DataError> error = null)
        {
            //---> Build the Uri, always https
            Uri uri = new Uri(String.Format("https://{0}{1}", _networkParameters.RestAPIHostName, endpoint));

            BestHTTP.HTTPRequest request = new BestHTTP.HTTPRequest(uri, httpMethod,
                (req, resp) =>
                {
                    //---> Check if the response is an error
                    DataError dataError = JsonUtility.FromJson<DataError>(resp.DataAsText);

                    if (dataError != null && !String.IsNullOrEmpty(dataError.error_description))
                    {
                        if (error == null && ErrorEvent != null)
                            EventManager.Instance.QueueEvent<string>(ErrorEvent, dataError.error_description);
                        else if (error != null)
                            error(dataError);
                    }
                    //---> If not an error, call the data response callback
                    else
                    {
                        if (req.State == HTTPRequestStates.TimedOut && RequestTimedOutEvent != null)
                            RequestTimedOutEvent();
                        else
                            dataResponse(resp.DataAsText);
                    }
                });

            //--- Header
            if (!string.IsNullOrEmpty(accessToken))
                request.AddHeader("Authorization", "bearer " + accessToken);

            request.AddHeader("User-Agent", GetUserAgent());
            //---

            if (httpMethod == HTTPMethods.Post)
            {
                request.AddHeader("Cache-Control", "no-cache");

                if (transformParametersToJson)
                    request.AddHeader("Content-Type", "application/json; charset=UTF-8");
                else
                    request.AddHeader("Content-Type", "application/x-www-form-urlencoded");

                if (parameters != null)
                {
                    if (transformParametersToJson)
                    {
                        string json = MiniJSON.Json.Serialize(parameters);
                        request.RawData = Encoding.UTF8.GetBytes(json);
                    }
                    else
                    {
                        foreach (string key in parameters.Keys)
                        {
                            request.AddField(key, parameters[key].ToString());
                        }
                    }
                }
            }

            request.CustomCertificateVerifyer = _customVerifyer;
            request.UseAlternateSSL = true;

            if (!IsInTestMode)
                request.Send();

            return request;
        }

        private HTTPRequest RequestCall(string accessToken, string endpoint, bool transformParametersToJson, Hashtable parameters, BestHTTP.HTTPMethods httpMethod, Action<Data> dataResponse, Action<DataError> error = null)
        {
            return RequestCall(accessToken, endpoint, transformParametersToJson, parameters, httpMethod,
            (string response) =>
            {
                DataContainer dataContainer = new DataContainer();
                if (!String.IsNullOrEmpty(response))
                {
                    dataContainer = JsonUtility.FromJson<DataContainer>(response);
                }

                dataResponse(dataContainer.data);
            }
            , error);
        }

        /// <summary>
        /// Connection to the DoW Server. Must be called first to obtain the access token
        /// </summary>
        public HTTPRequest Connect()
        {
            string endpoint = "/main/v2/oauth/token";

            Hashtable parameters = new Hashtable();

            parameters.Add("grant_type", "client_credentials");
            parameters.Add("client_id", _networkParameters.ClientId);
            parameters.Add("client_secret", _networkParameters.ClientSecret);
            parameters.Add("scope", "public");

            return RequestCallPost(string.Empty, endpoint, false, parameters,
                (string responseText) =>
                {
                    Debug.Log("Connected");

                    _authenticationTokens = JsonUtility.FromJson<AuthenticationTokens>(responseText);

                    if (ConnectionSuccessEvent != null)
                    {
                        EventManager.Instance.QueueEvent(ConnectionSuccessEvent, _authenticationTokens);
                    }
                },
                (DataError error) =>
                {
                    Debug.LogError(error.error_description);
                }
            );
        }

        /// <summary>
        /// Search a DoW user by his email. More informations here : https://apidoc.daysofwonder.com/#api-user-SearchByEmail
        /// </summary>
        /// <param name="email">Email to search</param>
        public HTTPRequest SearchByEmail(string accessToken, string email)
        {
            string endpoint = String.Format("/main/v1/users?email={0}", Uri.EscapeDataString(email));

            return RequestCallGet(accessToken, endpoint,
            (Data data) =>
            {
                if (data.users.Length > 0)
                {
                    DataUser user = data.users[0];

                    if (EmailFoundEvent != null)
                        EventManager.Instance.QueueEvent<DataUser>(EmailFoundEvent, user);
                }
                else
                {
                    if (EmailNotFoundEvent != null)
                        EventManager.Instance.QueueEvent(EmailNotFoundEvent);
                }
            });
        }

        /// <summary>
        /// Search a DoW User by his login. More informations here : https://apidoc.daysofwonder.com/#api-user-UserSignup
        /// </summary>
        /// <param name="login">Login to search</param>
        /// <param name="useWildcardAtTheEnd">Add a wildcard at the end of the login</param>
        public HTTPRequest SearchByLogin(string accessToken, string login, bool useWildcardAtTheEnd)
        {
            string endpoint = String.Format("/main/v1/users?login={0}{1}", Uri.EscapeDataString(login), useWildcardAtTheEnd ? Uri.EscapeDataString("%") : "");

            return RequestCallGet(accessToken, endpoint,
            (Data data) =>
            {
                if (data.users.Length > 0)
                {
                    DataUser user = data.users[0];

                    if (LoginFoundEvent != null)
                        EventManager.Instance.QueueEvent<DataUser>(LoginFoundEvent, user);
                }
                else
                {
                    if (LoginNotFoundEvent != null)
                        EventManager.Instance.QueueEvent(LoginNotFoundEvent);
                }
            });
        }


        /// <summary>
        /// Authentication of a user on the DoW server
        /// </summary>
        /// <param name="login">Login of the user</param>
        /// <param name="password">Password of the user</param>
        public HTTPRequest Authentication(string login, string password)
        {
            string endpoint = "/main/v2/oauth/token";

            Hashtable parameters = new Hashtable();

            parameters.Add("grant_type", "password");
            parameters.Add("client_id", _networkParameters.ClientId);
            parameters.Add("client_secret", _networkParameters.ClientSecret);
            parameters.Add("username", login);
            parameters.Add("password", password);

            return RequestCallPost(string.Empty, endpoint, false, parameters,
            (string responseText) =>
            {
                _authenticationTokens = JsonUtility.FromJson<AuthenticationTokens>(responseText);

                if (AuthenticationSuccessEvent != null)
                    EventManager.Instance.QueueEvent(AuthenticationSuccessEvent, _authenticationTokens);
            },
            (DataError dataError) =>
            {
                if (AuthenticationErrorEvent != null)
                    EventManager.Instance.QueueEvent<string>(AuthenticationErrorEvent, dataError.error_description);
            });
        }

        /// <summary>
        /// Authentication of a user on the DoW server with a refresh token
        /// </summary>
        ///<param name="refreshToken">Refresh token</param>
        public HTTPRequest AuthenticationWithRefreshToken(string refreshToken)
        {
            string endpoint = "/main/v2/oauth/token";

            Hashtable parameters = new Hashtable();
            parameters.Add("grant_type", "refresh_token");
            parameters.Add("client_id", _networkParameters.ClientId);
            parameters.Add("client_secret", _networkParameters.ClientSecret);
            parameters.Add("refresh_token", refreshToken);

            return RequestCallPost(string.Empty, endpoint, false, parameters,
            (string responseText) =>
            {
                _authenticationTokens = JsonUtility.FromJson<AuthenticationTokens>(responseText);

                if (AuthenticationSuccessEvent != null)
                    EventManager.Instance.QueueEvent(AuthenticationSuccessEvent, _authenticationTokens);
            },
            (DataError dataError) =>
            {
                if (AuthenticationErrorEvent != null)
                    EventManager.Instance.QueueEvent<string>(AuthenticationErrorEvent, dataError.error_description);
            });
        }

        /// <summary>
        /// Create a DoW user account
        /// </summary>
        /// <param name="login">Login of the user</param>
        /// <param name="password">Password of the user</param>
        /// <param name="email">Email of the user</param>
        /// <param name="subscribeNewsletter">Subscribe or not to the DoW newsletter</param>
        public HTTPRequest CreateAccount(string accessToken, string login, string password, string email, bool subscribeNewsletter)
        {
            string endpoint = "/main/v1/user";

            Hashtable parameters = new Hashtable();
            parameters.Add("login_name", login);
            parameters.Add("password", password);
            parameters.Add("email", email);
            parameters.Add("newsletter", subscribeNewsletter);

            return RequestCallPost(accessToken, endpoint, true, parameters,
            (Data data) =>
            {
                DataUser user = new DataUser(data.user_id, data.login_name);

                if (AccountCreatedEvent != null)
                    EventManager.Instance.QueueEvent<DataUser>(AccountCreatedEvent, user);
            },
            (DataError dataError) =>
            {
                if (AccountCreationErrorEvent != null)
                    EventManager.Instance.QueueEvent<string>(AccountCreationErrorEvent, dataError.error_description);
            });
        }

        /// <summary>
        /// Reset the password of a user
        /// </summary>
        /// <param name="userID">DoW user ID</param>
        public HTTPRequest ResetPassword(string accessToken, int userID)
        {
            string endpoint = String.Format("/main/v1/user/{0}/password", userID);

            return RequestCallDelete(accessToken, endpoint,
            (Data data) =>
            {
                if (LostPasswordEmailSentEvent != null)
                    EventManager.Instance.QueueEvent(LostPasswordEmailSentEvent);
            },
            (DataError dataError) =>
            {
                if (LostPasswordErrorEvent != null)
                    EventManager.Instance.QueueEvent<string>(LostPasswordErrorEvent, dataError.error_description);
            });
        }

        /// <summary>
        /// X509 Certificate verifier
        /// </summary>
        private class CustomVerifier : Org.BouncyCastle.Crypto.Tls.ICertificateVerifyer
        {
            private DoWNetworkParameters _networkParameters = null;

            public CustomVerifier(DoWNetworkParameters networkParameters)
            {
                _networkParameters = networkParameters;
            }

            public bool IsValid(Uri serverUri, Org.BouncyCastle.Asn1.X509.X509CertificateStructure[] certs)
            {
                bool isValid = false;

                //for (int i = 0; i < certs.Length; i++)
                //{
                //    string publicKeyServer = certs[i].SubjectPublicKeyInfo.PublicKeyData.GetString();
                //    Debug.Log(publicKeyServer);
                //}

                for (int i = 0; i < certs.Length && !isValid; i++)
                {
                    string publicKeyServer = certs[i].SubjectPublicKeyInfo.PublicKeyData.GetString();

                    foreach (string key in _networkParameters.RestAPIPinPublicKeys)
                    {
                        if (publicKeyServer == key)
                        {
                            isValid = true;
                            break;
                        }
                    }
                }

                return isValid;
            }
        }
    }
}
