#if UNITY_ANDROID && !UNITY_EDITOR
#define MOBILE_ANDROID
#endif
#if UNITY_IOS && !UNITY_EDITOR
#define MOBILE_IOS
#endif
#if MOBILE_ANDROID || MOBILE_IOS
#define MOBILE
#endif

using System;
using System.Threading.Tasks;
#if MOBILE_ANDROID
// not able to use File apis for reading from StreamingAssets
using UnityEngine.Networking;
#endif
using UnityEngine;
using Debug = UnityEngine.Debug;
using System.IO;
using SingularityGroup.HotReload.Localization;

namespace SingularityGroup.HotReload {
    // entrypoint for Unity Player builds. Not necessary in Unity Editor.
    internal static class PlayerEntrypoint {
        /// Set when behaviour is created, when you access this instance through the singleton,
        /// you can assume that this field is not null.
        /// <remarks>
        /// In Player code you can assume this is set.<br/>
        /// When in Editor this is usually null.
        /// </remarks>
        static BuildInfo buildInfo { get; set; }

        /// In Player code you can assume this is set (not null)
        public static BuildInfo PlayerBuildInfo => buildInfo;

        #if ENABLE_MONO
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
        #endif
        private static void InitOnAppLoad() {
            UnityHelper.Init();
            Translations.LoadDefaultLocalization();
            bool onlyPrefabMissing;
            if (!IsPlayerWithHotReload(out onlyPrefabMissing)) {
                if (onlyPrefabMissing) {
                    Log.Warning(Localization.Translations.Logging.HotReloadNotAvailableBuildSettings);
                }
                return;
            }

            TryAutoConnect().Forget();
        }

        static async Task TryAutoConnect() {
            try {
                buildInfo = await GetBuildInfo();
            } catch (Exception e) {
                if (e is IOException) {
                    Log.Warning(Localization.Translations.Logging.HotReloadNotAvailableBuildSettings);
                } else {
                    Log.Error($"{Localization.Translations.Errors.UnknownExceptionReadingBuildInfo}\n{e.GetType().Name}: {e.Message}");
                }
                return;
            }
            if (buildInfo == null) {
                Log.Error(Localization.Translations.Errors.BuildInfoNotFound);
                return;
            }

            CodePatcher.I.debuggerCompatibilityEnabled = true;
            CodePatcher.I.disableTelemetry = buildInfo.disableTelemetry;

            try {
                var customIp = PlayerPrefs.GetString("HotReloadRuntime.CustomIP", "");
                if (!string.IsNullOrEmpty(customIp)) {
                    buildInfo.buildMachineHostName = customIp;
                }
                var customPort = PlayerPrefs.GetString("HotReloadRuntime.CustomPort", "");
                if (!string.IsNullOrEmpty(customPort)) {
                    buildInfo.buildMachinePort = int.Parse(customPort);
                }

                if (buildInfo.BuildMachineServer == null) {
                    Prompts.ShowRetryDialog(null);
                } else {
                    // try reach server running on the build machine.
                    TryConnect(buildInfo.BuildMachineServer, auto: true).Forget();
                }
            } catch (Exception ex) {
                Log.Exception(ex);
            }
        }

        public static Task TryConnectToIpAndPort(string ip, int port) {
            ip = ip.Trim();
            if (buildInfo == null) {
                throw new ArgumentException(Localization.Translations.Logging.BuildInfoNotFound);
            }
            buildInfo.buildMachineHostName = ip;
            buildInfo.buildMachinePort = port;
            PlayerPrefs.SetString("HotReloadRuntime.CustomIP", ip);
            PlayerPrefs.SetString("HotReloadRuntime.CustomPort", port.ToString());
            return TryConnect(buildInfo.BuildMachineServer, auto: false);
        }

        public static async Task TryConnect(PatchServerInfo serverInfo, bool auto) {
            // try reach server running on the build machine.
            var handshake = PlayerCodePatcher.UpdateHost(serverInfo);
            await Task.WhenAny(handshake, Task.Delay(TimeSpan.FromSeconds(40)));
            await ThreadUtility.SwitchToMainThread();
            var handshakeResults = await handshake;
            var handshakeOk = handshakeResults.HasFlag(ServerHandshake.Result.Verified);
            if (!handshakeOk) {
                Log.Debug("ShowRetryPrompt because handshake result is {0}", handshakeResults);
                Prompts.ShowRetryDialog(serverInfo, handshakeResults, auto);
                // cancel trying to connect. They can use the retry button
                PlayerCodePatcher.UpdateHost(null).Forget();
            }

            Log.Info(string.Format(Localization.Translations.Logging.ServerHealthyAfterHandshake, handshakeOk));
        }

        /// on Android, streaming assets are inside apk zip, which can only be read using unity web request
        private static async Task<BuildInfo> GetBuildInfo() {
            var path = BuildInfo.GetStoredPath();
            #if MOBILE_ANDROID
            var json = await RequestHelper.GetAsync(path);
            return await Task.Run(() => BuildInfo.FromJson(json));
            #else
            return await Task.Run(() => {
                return BuildInfo.FromJson(File.ReadAllText(path));
            });
            #endif
        }

        public static bool IsPlayer() => !Application.isEditor;

        public static bool IsPlayerWithHotReload() {
            bool _;
            return IsPlayerWithHotReload(out _);
        }

        public static bool IsPlayerWithHotReload(out bool onlyPrefabMissing) {
            onlyPrefabMissing = false;
            if (!IsPlayer() || !RuntimeSupportsHotReload || !HotReloadSettingsObject.I.IncludeInBuild) {
                return false;
            }
            onlyPrefabMissing = !HotReloadSettingsObject.I.PromptsPrefab;
            return !onlyPrefabMissing;
        }
        
        public static bool RuntimeSupportsHotReload {
            get {
                #if DEVELOPMENT_BUILD && ENABLE_MONO
                return true;
                #else
                return false;
                #endif
            }
        }
    }
}
