using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Cysharp.Threading.Tasks;
using RebootKit.Engine.Console;
using RebootKit.Engine.Extensions;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Input;
using RebootKit.Engine.Network;
using RebootKit.Engine.Simulation;
#if RR_STEAM
using RebootKit.Engine.Steam;
#endif
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using Assert = UnityEngine.Assertions.Assert;
using Logger = RebootKit.Engine.Foundation.Logger;
using Object = UnityEngine.Object;

[assembly: InternalsVisibleTo("RebootKit.Editor")]

namespace RebootKit.Engine.Main {
    public static class RR {
        static readonly Logger s_Logger = new Logger("RR");

        [ConfigVar("con.write_log", 1, "Enables writing game log to console output")]
        static ConfigVar s_WriteLogToConsole;

        internal static EngineConfigAsset EngineConfig;

        static AsyncOperationHandle<SceneInstance> s_MainMenuSceneHandle;
        
        internal static GameObject SystemGameObject;
        internal static AppLoopManager LoopManager;

        internal static NetworkSystem Network;
        internal static byte NetworkProtocolVersion {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get {
                return EngineConfig.protocolVersion;
            }
        }

        internal static ConsoleService Console { get; private set; }
        public static InputService Input { get; private set; }
        public static WorldService World { get; private set; }

        public static Camera MainCamera { get; internal set; }

        public static Game GameInstance { get; internal set; }

        // Lifecycle API

        // @NOTE: This method is called at the very start of the game, when boot scene loaded.
        internal static async UniTask InitAsync(EngineConfigAsset configAsset, CancellationToken cancellationToken) {
            Assert.IsNotNull(configAsset, "Config asset is required");

            EngineConfig = configAsset;

            s_Logger.Info("Initializing");

            s_Logger.Info("Registering core services");
            Console = new ConsoleService();
            Input = new InputService(EngineConfig.inputConfig);
            World = new WorldService();
            
            SystemGameObject = new GameObject("__RebootReality__");
            Object.DontDestroyOnLoad(SystemGameObject);

            LoopManager = SystemGameObject.AddComponent<AppLoopManager>();

#if RR_STEAM
            await SteamManager.InitializeAsync(cancellationToken);
            Network = new NetworkSystem(new SteamNetworkManager());
#else
            Network = new NetworkSystem(new UnityNetworkManager());
#endif

            await InitializeAssetsAsync(cancellationToken);
        }

        // @NOTE: This method is called after the main scene is loaded.
        internal static async UniTask RunAsync(CancellationToken cancellationToken) {
            LoopManager.Add(Tick);

            await OpenMainMenuAsync(cancellationToken);

#if UNITY_EDITOR
            string scriptContent = UnityEditor.EditorPrefs.GetString("RebootKitEditor.OnGameRunScriptContent", "");
            s_Logger.Info($"Executing script: {scriptContent}");

            if (!string.IsNullOrEmpty(scriptContent)) {
                foreach (string cmd in scriptContent.Split('\n')) {
                    s_Logger.Info($"Executing command: {cmd}");
                    Console.Execute(cmd);
                }
            }
#endif
        }

        internal static void Shutdown() {
            s_Logger.Info("Shutting down");

            if (GameInstance is not null) {
                Object.Destroy(GameInstance);
                GameInstance = null;
            }

            Network.Dispose();
            Network = null;

#if RR_STEAM
            SteamManager.Shutdown();
#endif
            World.Dispose();
            Input.Dispose();
            Console.Dispose();
            
            LoopManager = null;
            if (SystemGameObject.OrNull() != null) {
                Object.Destroy(SystemGameObject);
            }
        }

        // Assets API
        static readonly List<WorldConfigAsset> s_WorldConfigsAssets = new List<WorldConfigAsset>();

        public static IReadOnlyList<WorldConfigAsset> WorldConfigsAssets => s_WorldConfigsAssets;

        static async UniTask InitializeAssetsAsync(CancellationToken cancellationToken) {
            s_WorldConfigsAssets.Clear();

            s_Logger.Info("Loading game assets");

            await Addressables.LoadAssetsAsync<WorldConfigAsset>("world", asset => { s_WorldConfigsAssets.Add(asset); })
                              .ToUniTask(cancellationToken: cancellationToken);
        }

        public static WorldConfigAsset GetWorldConfigAsset(string name) {
            if (string.IsNullOrEmpty(name)) {
                throw new ArgumentException("World config name cannot be null or empty", nameof(name));
            }

            WorldConfigAsset worldConfig = s_WorldConfigsAssets.Find(asset => asset.Config.name.Equals(name, StringComparison.Ordinal));
            if (worldConfig is null) {
                throw new KeyNotFoundException($"World config '{name}' not found");
            }

            return worldConfig;
        }

        //
        // @MARK: Game
        //
        internal static async UniTask OpenMainMenuAsync(CancellationToken cancellationToken) {
            s_Logger.Info("Opening main menu");

            World.Unload();

            if (!EngineConfig.mainMenuScene.RuntimeKeyIsValid()) {
                s_Logger.Error("Main menu scene is not set in EngineConfig");
                return;
            }

            s_MainMenuSceneHandle = Addressables.LoadSceneAsync(EngineConfig.mainMenuScene, LoadSceneMode.Additive);
            await s_MainMenuSceneHandle;
        }

        internal static void CloseMainMenu() {
            if (!s_MainMenuSceneHandle.IsValid()) {
                return;
            }

            Addressables.UnloadSceneAsync(s_MainMenuSceneHandle);
        }

        public static void SetServerWorld(string worldID) {
            if (!IsServer()) {
                s_Logger.Error("Cannot set server world. Not a server instance.");
                return;
            }

            if (GameInstance is null) {
                s_Logger.Error("Game is not initialized. Cannot set server world.");
                return;
            }

            s_Logger.Info($"Setting server world: {worldID}");

            WorldConfigAsset worldConfigAsset = GetWorldConfigAsset(worldID);
            if (worldConfigAsset is null) {
                s_Logger.Error($"World '{worldID}' not found");
                return;
            }

            Network.SetCurrentWorld(worldID);
        }

        public static Actor SpawnLocalOnlyActor(AssetReferenceGameObject assetReference,
                                                Vector3 position,
                                                Quaternion rotation) {
            return Network.Actors.SpawnLocalOnlyActor(assetReference, position, rotation);
        }

        public static Actor SpawnActor(AssetReferenceGameObject assetReference,
                                       Vector3 position,
                                       Quaternion rotation) {
            if (!IsServer()) {
                s_Logger.Error("Cannot spawn actor. Not a server instance.");
                return null;
            }

            if (Network is null) {
                s_Logger.Error("NetworkSystemInstance is not initialized. Cannot spawn actor.");
                return null;
            }

            if (!assetReference.RuntimeKeyIsValid()) {
                s_Logger.Error("Asset reference is not valid. Cannot spawn actor.");
                return null;
            }

            s_Logger.Info($"Spawning actor from asset reference: {assetReference.RuntimeKey}");
            return Network.Actors.SpawnActor(assetReference, position, rotation);
        }

        public static void DestroyActor(Actor actor) {
            if (actor == null) {
                s_Logger.Error("Cannot destroy actor. Actor is null.");
                return;
            }

            Network.Actors.DestroyActor(actor);
        }

        public static Actor FindSpawnedActor(ushort actorID) {
            if (Network is null) {
                s_Logger.Error("NetworkSystemInstance is not initialized. Cannot find actor.");
                return null;
            }

            Actor actor = Network.Actors.FindActorByID(actorID);
            if (actor is null) {
                s_Logger.Error($"Actor with ID {actorID} not found");
            }

            return actor;
        }

        public static void PossessActor(ulong clientID, ushort actorID) {
            if (!IsServer()) {
                s_Logger.Error("Only server can possess actors for clients.");
                return;
            }

            if (Network == null) {
                s_Logger.Error("Network is not initialized. Cannot possess actor.");
                return;
            }

            Network.SendPossessedActor(clientID, actorID);
        }

        public static IEnumerable<Actor> Actors() {
            foreach (Actor actor in Network.Actors.InSceneActors) {
                yield return actor;
            }
            
            foreach (Actor actor in Network.Actors.SpawnedActors) {
                yield return actor;
            }
        }

        //
        // @MARK: Logging API
        //
        public static void Log(string message) {
            Debug.Log(message);
            Console?.WriteToOutput(message);
        }

        public static void LogWarning(string message) {
            Debug.LogWarning(message);
            Console?.WriteToOutput(message);
        }

        public static void LogError(string message) {
            Debug.LogError(message);
            Console?.WriteToOutput(message);
        }

        public static void WriteToConsole(string message) {
            Console?.WriteToOutput(message);
        }

        //
        // @MARK: Config Variables
        //
        public static ConfigVar CVarIndex(string name, int defaultValue = -1) {
            ConfigVar cvar = ConfigVarsContainer.Get(name);
            if (cvar != null) {
                return cvar;
            }

            cvar = new ConfigVar(name, defaultValue);
            ConfigVarsContainer.Register(cvar);
            return cvar;
        }

        public static ConfigVar CVarNumber(string name, double defaultValue = 0) {
            ConfigVar cvar = ConfigVarsContainer.Get(name);
            if (cvar != null) {
                return cvar;
            }

            cvar = new ConfigVar(name, defaultValue);
            ConfigVarsContainer.Register(cvar);
            return cvar;
        }

        public static ConfigVar CVarString(string name, string defaultValue = "") {
            ConfigVar cvar = ConfigVarsContainer.Get(name);
            if (cvar != null) {
                return cvar;
            }

            cvar = new ConfigVar(name, defaultValue);
            ConfigVarsContainer.Register(cvar);
            return cvar;
        }

        //
        // @MARK: Network API
        //
        public static bool IsServer() {
            return Network != null && Network.Manager.IsServer();
        }

        public static bool IsClient() {
            return Network != null && Network.Manager.IsClient();
        }

        public static void StartHost() {
            if (IsServer() || IsClient()) {
                s_Logger.Error("Already hosting a server or connected as a client");
                return;
            }

            // @TODO: Handle failures

            s_Logger.Info("Starting host");
            if (!Network.Manager.StartHost()) {
                s_Logger.Error("Failed to start host.");
                return;
            }
        }

        public static void StopHost() {
            Network.Manager.StopHost();
        }

        public static void Connect() {
            if (IsClient()) {
                s_Logger.Error("Already connected to a server");
                return;
            }

            s_Logger.Info("Connecting to server.");
            Network.Manager.StartClient();
        }

        public static void ConnectWithSteamID(ulong steamId) {
#if RR_STEAM
            if (IsClient()) {
                s_Logger.Error("Already connected to a server");
                return;
            }

            s_Logger.Info($"Connecting to server with Steam ID: {steamId}");
            if (Network.Manager is SteamNetworkManager steamNetworkManager) {
                steamNetworkManager.TargetSteamID = steamId;
                Network.Manager.StartClient();
            } else {
                s_Logger.Error("Network transport is not FacepunchTransport. Cannot connect with Steam ID.");
            }
#else
            s_Logger.Error("Steam integration is not enabled. Cannot connect with Steam ID.");
#endif
        }

        public static void Disconnect() {
            Network.Manager.Disconnect();
        }

        public static void SendChatMessage(string message) {
            if (!IsClient()) {
                s_Logger.Error("Cannot send chat message. Not connected to a server.");
                return;
            }

            if (string.IsNullOrEmpty(message)) {
                return;
            }

            throw new NotSupportedException("Cannot send chat message. Not connected to a server.");
        }

        static void Tick(float dt) {
            Network.Tick(dt);
        }

        internal static void OnServerStarted() {
            s_Logger.Info("Server started");

            GameInstance = Object.Instantiate(EngineConfig.gamePrefab);
        }

        internal static void OnServerStopped() {
            s_Logger.Info("Server stopped");

            if (GameInstance is not null) {
                Object.Destroy(GameInstance.gameObject);
                GameInstance = null;
            }
        }

        internal static void OnClientStarted() {
            if (IsServer()) {
                return;
            }

            GameInstance = Object.Instantiate(EngineConfig.gamePrefab);
        }

        internal static void OnClientStopped() {
            if (IsServer()) {
                return;
            }

            World.Unload();
            OpenMainMenuAsync(CancellationToken.None).Forget();

            if (GameInstance is not null) {
                Object.Destroy(GameInstance.gameObject);
                GameInstance = null;
            }
        }

        // Console Commands
        [RCCMD("say", "Sends chat message")]
        static void Say(string[] args) {
            if (args.Length < 2) {
                Console.WriteToOutput("Usage: say <message>");
                return;
            }

            string message = string.Join(" ", args, 1, args.Length - 1);
            SendChatMessage(message);
        }
    }
}