using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using VRC.Core;

namespace VRC.SDK3.ClientSim
{
    public static class ClientSimRuntimeLoader
    {
        private const string EDITOR_ONLY_TAG = "EditorOnly";
        
        // Used in tests to prevent the runtime initialized methods from executing.
        private static bool _isInTestMode = false;
        private static ClientSimSettings _testSettingsOverride;
        private static ClientSimEventDispatcher _testEventDispatcherOverride;

        #region ClientSim Initialization

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        private static void OnBeforeSceneLoad()
        {
            StartClientSim(GetSettings(), GetEventDispatcher());
        }

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
        private static void OnAfterSceneLoad()
        {
            // Delete all editor only objects before creating ClientSim.
            DestroyEditorOnly(GetSettings());
            
#if VRC_ENABLE_PLAYER_PERSISTENCE
            if (ClientSimMain.TryGetInstance(out var instance))
                instance.EnablePlayerObjects();
#endif
        }

        #endregion

        #region Test Methods

        [PublicAPI]
        public static void BeginUnityTesting(
            ClientSimSettings testSettingsOverride,
            ClientSimEventDispatcher testEventDispatcherOverride = null)
        {
            _isInTestMode = true;
            _testSettingsOverride = testSettingsOverride;
            _testEventDispatcherOverride = testEventDispatcherOverride;
            
            if (_testSettingsOverride == null)
            {
                _testSettingsOverride = new ClientSimSettings();
            }
        }
        
        [PublicAPI]
        public static void EndUnityTesting()
        {
            _isInTestMode = false;
            _testSettingsOverride = null;
        }
        
        [PublicAPI]
        public static bool IsInUnityTest()
        {
            return _isInTestMode;
        }
        
        #endregion

        private static ClientSimSettings GetSettings()
        {
            return IsInUnityTest() ? _testSettingsOverride : ClientSimSettings.Instance;
        }
        
        private static ClientSimEventDispatcher GetEventDispatcher()
        {
            return IsInUnityTest() ? _testEventDispatcherOverride : null;
        }
        
        private static bool IsClientSimEnabled(ClientSimSettings settings)
        {
            return 
                settings.enableClientSim &&
                Application.isPlaying;
        }

        // Start client sim with the given settings.
        // Optional event dispatcher can be passed in to listen to startup events. Mainly used in tests.
        public static void StartClientSim(
            ClientSimSettings settings,
            IClientSimEventDispatcher eventDispatcher = null)
        {
            if (!IsClientSimEnabled(settings))
            {
                return;
            }
            
            // Delete all editor only objects before creating ClientSim.
            DestroyEditorOnly(settings);

            ClientSimMain.CreateInstance(settings, eventDispatcher);

            // TODO: Below is disabled for now because the rest of the ClientSim initialization code doesn't work if it's called with a delay.
            // Currently, not loading RemoteConfig will not cause any issues, but it may in the future, so this is left in as a reminder.

            // Create ClientSim Instance later
            /*void CreateClientSimInstance() => ClientSimMain.CreateInstance(settings, eventDispatcher);

            // If the Remote Config is not initialized, attempt init before starting ClientSim
            // Start ClientSim after attempt, regardless of success or failure
            if (!ConfigManager.RemoteConfig.IsInitialized())
            {
                API.SetOnlineMode(true);
                ConfigManager.RemoteConfig.Init(CreateClientSimInstance, CreateClientSimInstance);
            }
            // Otherwise, start ClientSim immediately
            else
            {
                CreateClientSimInstance();
            }*/
        }

        private static void DestroyEditorOnly(ClientSimSettings settings)
        {
            if (!settings.enableClientSim || !settings.deleteEditorOnly)
            {
                return;
            }

            List<GameObject> rootObjects = new List<GameObject>();
            Scene scene = SceneManager.GetActiveScene();
            scene.GetRootGameObjects(rootObjects);
            Queue<GameObject> queue = new Queue<GameObject>(rootObjects);
            while (queue.Count > 0)
            {
                GameObject obj = queue.Dequeue();
                if (obj.CompareTag(EDITOR_ONLY_TAG))
                {
                    obj.Log($"Deleting editor only object: {Tools.GetGameObjectPath(obj)}");
                    Object.DestroyImmediate(obj);
                }
                else
                {
                    for (int child = 0; child < obj.transform.childCount; ++child)
                    {
                        queue.Enqueue(obj.transform.GetChild(child).gameObject);
                    }
                }
            }
        }
    }
}