﻿using AsmodeeDigital.Common.Plugin.Domain.CrossPromo;
using AsmodeeDigital.Common.Plugin.Domain.Data;
using AsmodeeDigital.Common.Plugin.Manager.Event;
using System;
using System.Collections;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using UnityEngine;

namespace AsmodeeDigital.Common.Plugin.Network
{
    public 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<Product> ReceivingProductEvent;

        public event Action<GroupProduct> ReceivingGroupProductEvent;

        public event Action LostPasswordEmailSentEvent;

        public event Action<string> LostPasswordErrorEvent;

        public event Action<string> ErrorEvent;
        #endregion

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

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

        public RestAPI(DoWNetworkParameters networkParameters, CrossPromoParameters promoParameters = null)
        {
            _networkParameters = networkParameters;
            _promoParameters = promoParameters;
        }

        /// <summary>
        /// Check the Pin Public Key
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="certificate"></param>
        /// <param name="chain"></param>
        /// <param name="sslPolicyErrors"></param>
        /// <returns></returns>
        public bool PinPublicKey(object sender, X509Certificate certificate, X509Chain chain,
                            SslPolicyErrors sslPolicyErrors)
        {
            if (null == certificate)
                return false;

            //Warn : If there is an error with GetPublicKeyString it's because the target in unity is Web.
            //       Please set the target to PC, iOS or Android
            String pk = certificate.GetPublicKeyString();

            if (pk.Equals(_networkParameters.RestAPIPinPublicKey))
                return true;

            return false;
        }

        /// <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 void RequestCall(string accessToken, string endpoint, WWWForm wwwForm, Hashtable parameters, string httpMethod, Action<string> dataResponse, Action<DataError> error = null)
        {
            string protocol = _networkParameters.UseSSL ? "https" : "http";
            string url = String.Format("{0}://{1}{2}", protocol, _networkParameters.RestAPIHostName, endpoint);

            HTTP.Request someRequest = null;

            if (wwwForm != null)
                someRequest = new HTTP.Request(httpMethod, url, wwwForm);
            else if (parameters != null)
                someRequest = new HTTP.Request(httpMethod, url, parameters);
            else
                someRequest = new HTTP.Request(httpMethod, url);

            someRequest.ValidateServerCertificate = PinPublicKey;

            if (!string.IsNullOrEmpty(accessToken))
                someRequest.AddHeader("Authorization", "bearer " + accessToken);

            someRequest.AddHeader("User-Agent", GetUserAgent());

            someRequest.Send((request) =>
            {
                DataError dataError = JsonUtility.FromJson<DataError>(request.response.Text);

                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);
                }
                else
                {
                    dataResponse(request.response.Text);
                }
            });
        }

        private void RequestCall(string accessToken, string url, WWWForm wwwForm, Hashtable parameters, string httpMethod, Action<Data> dataResponse, Action<DataError> error = null)
        {
            RequestCall(accessToken, url, wwwForm, parameters, httpMethod,
                (string response) =>
                {
                    DataContainer 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 void Connect()
        {
            string endpoint = "/main/v2/oauth/token";

            WWWForm form = new WWWForm();
            //form.AddField("Host", endpoint);
            form.AddField("Cache-Control", "no-cache");
            form.AddField("Content-Type", "application/x-www-form-urlencoded");
            form.AddField("grant_type", "client_credentials");
            form.AddField("client_id", _networkParameters.ClientId);
            form.AddField("client_secret", _networkParameters.ClientSecret);
            form.AddField("scope", "public");

            RequestCall(string.Empty, endpoint, form, null, "post",
                (string responseText) =>
                {
                    Debug.Log("Connected");

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

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

        /// <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 void SearchByEmail(string accessToken, string email)
        {
            string endpoint = String.Format("/main/v1/users?email={0}", Uri.EscapeDataString(email));

            RequestCall(accessToken, endpoint, null, null, "get", (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 void SearchByLogin(string accessToken, string login, bool useWildcardAtTheEnd)
        {
            string endpoint = String.Format("/main/v1/users?login={0}{1}", Uri.EscapeDataString(login), useWildcardAtTheEnd ? Uri.EscapeDataString("%") : "");

            RequestCall(accessToken, endpoint, null, null, "get",
            (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 void Authentication(string login, string password)
        {
            string endpoint = "/main/v2/oauth/token";

            WWWForm form = new WWWForm();
            //form.AddField("Host", url);
            form.AddField("Cache-Control", "no-cache");
            form.AddField("Content-Type", "application/x-www-form-urlencoded");
            form.AddField("grant_type", "password");
            form.AddField("client_id", _networkParameters.ClientId);
            form.AddField("client_secret", _networkParameters.ClientSecret);
            form.AddField("username", login);
            form.AddField("password", password);

            RequestCall(string.Empty, endpoint, form, null, "post",
            (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 void AuthenticationWithRefreshToken(string refreshToken)
        {
            string endpoint = "/main/v2/oauth/token";

            WWWForm form = new WWWForm();
            //form.AddField("Host", url);
            form.AddField("Cache-Control", "no-cache");
            form.AddField("Content-Type", "application/x-www-form-urlencoded");
            form.AddField("grant_type", "refresh_token");
            form.AddField("client_id", _networkParameters.ClientId);
            form.AddField("client_secret", _networkParameters.ClientSecret);
            form.AddField("refresh_token", refreshToken);

            RequestCall(string.Empty, endpoint, form, null, "post",
            (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 void CreateAccount(string accessToken, string login, string password, string email, bool subscribeNewsletter)
        {
            string endpoint = "/main/v1/user";

            Hashtable parameters = new Hashtable();
            parameters.Add("client_id", _networkParameters.ClientId);
            parameters.Add("client_secret", _networkParameters.ClientSecret);
            parameters.Add("login_name", login);
            parameters.Add("password", password);
            parameters.Add("email", email);
            parameters.Add("newsletter", subscribeNewsletter);

            RequestCall(accessToken, endpoint, null, parameters, "post",
            (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 void ResetPassword(string accessToken, int userID)
        {
            string endpoint = String.Format("/main/v1/user/{0}/password", userID);

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

        #region Cross Promo
        public void BannerRequest(string accessToken)
        {
            string endpoint = String.Format("/main/v2/showcase/banner/{0}/{1}/?debug={2}", _promoParameters.Channel, _promoParameters.Language, _promoParameters.DebugSecret);

            RequestCall(accessToken, endpoint, null, null, "get",
            (Data data) =>
            {
                if (ReceivingProductEvent != null)
                    EventManager.Instance.QueueEvent<Product>(ReceivingProductEvent, data.product);
            });
        }

        public void MoreGameRequest(string accessToken, MoreGamesFilters moreGamesFilters, int nbCol)
        {
            string endpoint = String.Format("/main/v2/showcase/games/{0}/{1}/?tag={2}&nbcol={3}&debug={4}", _promoParameters.Channel, _promoParameters.Language, moreGamesFilters.ToString().ToLower(), nbCol, _promoParameters.DebugSecret);

            RequestCall(accessToken, endpoint, null, null, "get",
            (Data data) =>
            {
                GroupProduct groupProduct = new GroupProduct();
                groupProduct.products = data.products;

                if (ReceivingGroupProductEvent != null)
                    EventManager.Instance.QueueEvent<GroupProduct>(ReceivingGroupProductEvent, groupProduct);
            });
        }

        public void InterstitialRequest(string accessToken, int nbCol)
        {
            string endpoint = String.Format("/main/v2/showcase/interstitial/{0}/{1}/?nbcol={2}&debug={3}", _promoParameters.Channel, _promoParameters.Language, nbCol, _promoParameters.DebugSecret);

            RequestCall(accessToken, endpoint, null, null, "get",
            (Data data) =>
            {
                GroupProduct groupProduct = new GroupProduct();
                groupProduct.products = data.products;

                if (ReceivingGroupProductEvent != null)
                    EventManager.Instance.QueueEvent<GroupProduct>(ReceivingGroupProductEvent, groupProduct);
            });
        }
        #endregion
    }
}
