using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using _Combo.Events;
using _Core;
using _Core.Storage;
using Newtonsoft.Json;
using Seayoo.ComboSDK.Windows;
using UnityEngine;
#if UNITY_STANDALONE
using Vuplex.WebView;
#endif

namespace _Combo
{
    public class Combo : Singleton<Combo>
    {
        // SDK 初始化状态
        private enum InitStatus
        {
            NotCalled,        // 未调用初始化
            Initializing,     // 正在初始化
            InitSuccess,      // 初始化成功
            InitFailure       // 初始化失败
        }

        private string defaultEndpoint = "https://api.seayoo.com";
        private SetupOptions setupOptions;
        private Action<ComboResult> setupFinishedAction;
        private InitStatus initStatus = InitStatus.NotCalled;
        // 存储需要执行的方法
        private MethodQueue methodQueue = new MethodQueue();
        // 存储需要执行的回调方法
        private MethodQueue callbackQueue = new MethodQueue();
        private List<string> setupSuccessIdpModules = new List<string>();
        private InternalError setupError;
        private bool isSetCache = false;

        public void Setup(SetupOptions options, Action<ComboResult> action)
        {
            EventSystem.Register(this);
            LanguageManager.Instance.Setup();
            setupFinishedAction = action;
            initStatus = InitStatus.Initializing;
            setupOptions = options;

            var gameId = options.gameId;
            var publishableKey = options.publishableKey;
            var endpoint = options.endpoint;

            try
            {
                CheckoutSetupArguments(gameId, publishableKey, endpoint);
            }
            catch (InternalError e)
            {
                initStatus = InitStatus.InitFailure;
                action.Invoke(ComboResult.CreateFail(e));
            }

            // 初始化 Manager
            var config = Configuration.Instance;
            if (config == null)
            {
                initStatus = InitStatus.InitFailure;
                var e = new InternalError(new InitializedError(InitializedErrorType.CONFIGURATION_FILE_NOT_EXIST));
                action.Invoke(ComboResult.CreateFail(e));
                Log.E(e.Message);
                return;
            }
            ErrorTrackingManager.Instance.Setup(config.Modules);
            var comboEndpoint = string.IsNullOrEmpty(endpoint) ? defaultEndpoint : endpoint;
            ComboClient.Setup(gameId, config.Distro, publishableKey, comboEndpoint, options.unitySdkVersion, options.unityVersion);
            GetParametersAction();
        }

        private void CheckoutSetupArguments(string gameId, string publishableKey, string endpoint)
        {
            List<string> missingArgs = new List<string>();
            if (string.IsNullOrEmpty(gameId))
            {
                missingArgs.Add("GameId");
            }
            if (string.IsNullOrEmpty(publishableKey))
            {
                missingArgs.Add("PublishableKey");
            }
            if (missingArgs.Count > 0)
            {
                string errorMessage = string.Join(" & ", missingArgs);
                Log.E($"{errorMessage} can't be empty, please checkout ComboSDKSetupOptions.");

                var invalidArgumentsError = new InvalidArgumentsError("setupArguments", errorMessage, InvalidArgumentsErrorType.INVALID_ARGUMENTS);

                throw new InternalError(errorMessage, invalidArgumentsError);
            }
        }

        private void GetParametersAction()
        {
            Log.I("GetParametersAction() called");
            if (initStatus == InitStatus.NotCalled)
            {
                callbackQueue.ExecuteAll();
                methodQueue.RemoveAll();
                return;
            }
            initStatus = InitStatus.Initializing;
            GlobalParameters.Instance.Setup(async () =>
            {
                Log.I("Get parameters success");
                var config = Configuration.Instance;
                if (GlobalParameters.Instance.SdkLocalhostHttpServerEnabled)
                {
                    HttpServer.Instance.StartServer();
                }
                IdPManager.Instance.Setup(config.Modules, setupOptions);
                StoreManager.Instance.Setup(config.Modules, new StoreParameters(setupOptions.endpoint, setupOptions.gameId, setupOptions.publishableKey, config.Distro, setupOptions.unitySdkVersion, setupOptions.unityVersion));
                LanguageManager.Instance.Setup();
#if UNITY_STANDALONE
                GamerManager.Instance.Setup(setupOptions.gameId);
                AnnouncementManager.Instance.Setup(setupOptions.gameId, config.Distro);
                ComplainManager.Instance.Setup(setupOptions.gameId);
                RedeemGiftCodeManager.Instance.Setup();
                SetWebViewCachePath();
                await SetWebUserAgent();
#endif
            }, e =>
            {
                Log.I($"Get parameters failed, error = {e}");
                SetupFailed(new SetupFailedEvent()
                {
                    error = e
                });
            });
        }

        [EventSystem.BindEvent]
        public void SetupSuccess(SetupSuccessEvent evt)
        {
            if (setupSuccessIdpModules.Contains(evt.idp))
                return;
            setupSuccessIdpModules.Add(evt.idp);
            if (setupSuccessIdpModules.Count == IdPManager.Instance.GetIdpModule().Count)
            {
                setupFinishedAction.Invoke(ComboResult.CreateSuccess());
                initStatus = InitStatus.InitSuccess;
                methodQueue.ExecuteAll();
            }
        }

        [EventSystem.BindEvent]
        public void SetupFailed(SetupFailedEvent evt)
        {
            setupError = evt.error;
            callbackQueue.ExecuteAll();
            methodQueue.RemoveAll();
            setupFinishedAction.Invoke(ComboResult.CreateFail(evt.error));
            ErrorTrackingManager.Instance.CaptureException(evt.error);
            initStatus = InitStatus.InitFailure;
        }

        public void Login(Action<ComboResult<UserModel>> action)
        {
            Log.I($"Login() Called");
            Action apiAction = () =>
            {
                IdPManager.Instance.Login(result => action.Invoke(result));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<UserModel>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public void Logout(Action<ComboResult<UserModel>> action)
        {
            Log.I($"Logout() Called");
            Action apiAction = () =>
            {
                IdPManager.Instance.Logout(result => action.Invoke(result));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<UserModel>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public void KickOut(Action<ComboResult<bool>> action)
        {
            Log.I($"KickOut() Called");
            IdPManager.Instance.KickOut(result => action.Invoke(result));
        }

        public UserModel GetLoginInfo()
        {
            return IdPManager.Instance.GetUser();
        }

        public string GetDistro()
        {
            return Configuration.Instance.Distro;
        }

        public void Purchase(string orderToken, Action<ComboResult<ComboSDKPaymentResult>> action)
        {
            Log.I($"Purchase() Called");
            Action apiAction = () =>
            {
                StoreManager.Instance.Purchase(orderToken, result => HandleResult<ComboSDKPaymentResult>(result, action));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<ComboSDKPaymentResult>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public Dictionary<string, string> GetParameters()
        {
            return GlobalParameters.Instance.GetParameters();
        }

        public bool IsFeatureAvailable(Feature feature)
        {
            switch (feature)
            {
                case Feature.UPDATE_GAME:
                    return false;
                case Feature.QUIT:
                    return false;
                case Feature.PRODUCT_QUANTITY:
                    return StoreManager.Instance.IsAvailableProductQuantity();
                case Feature.SEAYOO_ACCOUNT:
                    return IdPManager.Instance.IsFeatureAvailable(Feature.SEAYOO_ACCOUNT);
                case Feature.CONTACT_SUPPORT:
                    return IdPManager.Instance.IsFeatureAvailable(Feature.CONTACT_SUPPORT);
                default:
                    return false;
            }
        }

        public void GetDownloadUrl(Action<ComboResult<ComboSDKDownloadUrlResult>> action)
        {
            Log.I($"GetDownloadUrl() Called");
            Action apiAction = () =>
            {
                ComboClient.GetDownloadUrl(result =>
                {
                    if (result.IsSuccess)
                    {
                        var res = new ComboSDKDownloadUrlResult
                        {
                            downloadUrl = result.Data
                        };
                        action.Invoke(ComboResult<ComboSDKDownloadUrlResult>.CreateSuccess(res));
                    }
                    else
                    {
                        action.Invoke(ComboResult<ComboSDKDownloadUrlResult>.CreateFail(result.Error));
                    }
                });
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<ComboSDKDownloadUrlResult>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public LanguagePreference GetLanguagePreference()
        {
            return LanguageManager.Instance.GetLanguagePreference();
        }

        public void SetLanguagePreference(LanguagePreference language)
        {
            LanguageManager.Instance.SetLanguagePreference(language);
        }

        public string GetLanguageCode()
        {
            return I18n.GetLanguageCode();
        }

        private void HandleResult<T>(ComboResult<T> result, Action<ComboResult<T>> action)
        {
            if (result.IsSuccess)
            {
                action.Invoke(ComboResult<T>.CreateSuccess(result.Data));
            }
            else
            {
                action.Invoke(ComboResult<T>.CreateFail(result.Error));
            }
        }

        private void ProcessAction(Action apiAction, Action callbackAction)
        {
            if (initStatus == InitStatus.InitSuccess)
            {
                apiAction();
            }
            else
            {
                Log.I($"Init not completed, enqueuing action, init status = {initStatus}");
                methodQueue.Enqueue(apiAction);
                callbackQueue.Enqueue(callbackAction);
                if (initStatus != InitStatus.Initializing)
                {
                    GetParametersAction();
                }
            }
        }

        private InternalError GetConfigurationError()
        {
            Exception error = Configuration.GetLastException();
            if (error == null)
            {
                if (setupError != null)
                {
                    return setupError;
                }
                return new InternalError(new InitializedError(InitializedErrorType.INIT_FAILED));
            }
            else
            {
                if (error.GetType() == typeof(FileNotFoundException))
                {
                    return new InternalError(new InitializedError(InitializedErrorType.CONFIGURATION_FILE_NOT_EXIST, cause: error));
                }
                else
                {
                    return new InternalError(new InitializedError(InitializedErrorType.LOAD_CONFIGURATION_FILE_ERROR, cause: error));
                }
            }
        }

#if UNITY_STANDALONE
        public void ContactSupport()
        {
            UIController.Instance.ShowWebView(GlobalParameters.Instance.SeayooAccountSupportUrl, WebViewType.General, 100, 100);
        }

        public void OpenGameUrl(GameUrl gameUrl)
        {
            Dictionary<GameUrl, string> keyValuePairs = new Dictionary<GameUrl, string>
            {
                {GameUrl.USER_AGREEMENT, GlobalParameters.Instance.GameUrlUserAgreement},
                {GameUrl.PRIVACY_POLICY, GlobalParameters.Instance.GameUrlPrivacyPolicy},
                {GameUrl.PRIVACY_CHILDREN, GlobalParameters.Instance.GameUrlPrivacyChildren},
                {GameUrl.THIRD_PARTY, GlobalParameters.Instance.GameUrlThirdParty},
                {GameUrl.FANGCHENMI, GlobalParameters.Instance.GameUrlFangchenmi}
            };
            string url;
            keyValuePairs.TryGetValue(gameUrl, out url);
            UIController.Instance.ShowWebView(url, WebViewType.General, 100, 100);
        }

        public void OpenShortLink(string shortLink, Dictionary<string, string> gameData, Action<ComboResult<OpenShortLinkResult>> action)
        {
            Log.I($"OpenShortLink() Called");
            Action apiAction = () =>
            {
                GamerManager.Instance.OpenShortLink(shortLink, gameData, result => HandleResult<OpenShortLinkResult>(result, action));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<OpenShortLinkResult>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public void CheckAnnouncements(ComboSDKCheckAnnouncementsOptions opts, Action<ComboResult<ComboSDKCheckAnnouncementsResult>> action)
        {
            Log.I($"CheckAnnouncements() Called");
            Action apiAction = () =>
            {
                AnnouncementManager.Instance.CheckAnnouncements(opts, result => HandleResult<ComboSDKCheckAnnouncementsResult>(result, action));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<ComboSDKCheckAnnouncementsResult>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public void OpenAnnouncements(ComboSDKOpenAnnouncementsOptions opts, Action<ComboResult<ComboSDKOpenAnnouncementsResult>> action)
        {
            Log.I($"OpenAnnouncements() Called");
            Action apiAction = () =>
            {
                AnnouncementManager.Instance.OpenAnnouncements(opts, result => HandleResult<ComboSDKOpenAnnouncementsResult>(result, action));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<ComboSDKOpenAnnouncementsResult>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public void Complain(ComplainOptions opts, Action<ComboResult<ComplainResult>> action)
        {
            Log.I($"Complain() Called");
            Action apiAction = () =>
            {
                ComplainManager.Instance.Complain(opts, result => HandleResult<ComplainResult>(result, action));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<ComplainResult>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        public void RedeemGiftCode(RedeemGiftCodeOptions opts, Action<ComboResult<RedeemGiftCodeResult>> action)
        {
            Log.I($"RedeemGiftCode() Called");
            Action apiAction = () =>
            {
                RedeemGiftCodeManager.Instance.RedeemGiftCode(opts, result => HandleResult(result, action));
            };
            Action callbackAction = () =>
            {
                action.Invoke(ComboResult<RedeemGiftCodeResult>.CreateFail(GetConfigurationError()));
            };
            ProcessAction(apiAction, callbackAction);
        }

        private void SetWebViewCachePath()
        {
            try
            {
                if (isSetCache)
                {
                    return;
                }
                var key = WebViewCache.Instance.GetWebViewCachePathDicKey();
                var dic = JsonConvert.DeserializeObject<Dictionary<string, bool>>(UserPrefs.GetString(key));
                if (dic == null)
                {
                    dic = new Dictionary<string, bool>();
                    UserPrefs.SetString(key, JsonConvert.SerializeObject(dic));
                }
                var currentPath = WebViewCache.Instance.GetCurrentAvailableCachePath();
                string instanceCachePath = Path.Combine(Application.persistentDataPath, currentPath);

                StandaloneWebView.SetCachePath(instanceCachePath);
                dic[currentPath] = true;
                UserPrefs.SetString(key, JsonConvert.SerializeObject(dic));
                isSetCache = true;
                Log.I($"Cache path set to: {instanceCachePath}");
            }
            catch (Exception error)
            {
                Log.E($"Webview SetWebViewCachePath failed: {error}");
                ErrorTrackingManager.Instance.CaptureException(new InternalError(error));
            }
        }

        private async Task SetWebUserAgent()
        {
            try
            {
                var webView = Web.CreateWebView();
                await webView.Init(100, 100);
                webView.LoadUrl("about:blank");
                await webView.WaitForNextPageLoadToFinish();
                var userAgent = await webView.ExecuteJavaScript("navigator.userAgent");
                if (userAgent != null && userAgent.Contains("combo-sdk-windows"))
                {
                    webView.Dispose();
                    return;
                }
                webView.Dispose();
                Web.SetUserAgent(userAgent + $" combo-sdk-windows/{Seayoo.ComboSDK.Windows.Version.Version.SDKVersion}");
            }
            catch (Exception error)
            {
                Log.E($"Webview SetWebUserAgent failed: {error}");
                ErrorTrackingManager.Instance.CaptureException(new InternalError(error));
            }
        }
#endif
    }
}
