using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using Logger = RebootKit.Engine.Foundation.Logger;

namespace RebootKit.Engine.Simulation {
    public interface IWorldContext { }

    public class WorldService : IService {
        static readonly Logger s_Logger = new Logger(nameof(WorldService));

        enum WorldState {
            Unloaded,
            Loading,
            Loaded
        }

        WorldState m_WorldState = WorldState.Unloaded;
        WorldConfig m_Config;
        AsyncOperationHandle<SceneInstance> m_SceneInstance;
        
        enum ActorOrigin {
            InScene,
            Prefab
        }

        struct ActorData {
            public ActorOrigin Origin;
            public Actor Actor;
            
            public ActorData(ActorOrigin origin, Actor actor) {
                Origin = origin;
                Actor = actor;
            }
        }

        readonly List<ActorData> m_Actors = new List<ActorData>();

        public IWorldContext Context { get; private set; }
        
        public string WorldID {
            get {
                return string.IsNullOrEmpty(m_Config.name) ? string.Empty : m_Config.name;
            }
        }

        public WorldService() {
        }

        public void Dispose() {
            Unload();
        }
        
        public async UniTask LoadAsync(WorldConfig worldConfig, CancellationToken cancellationToken) {
            await UniTask.WaitWhile(() => m_WorldState == WorldState.Loading, cancellationToken: cancellationToken);

            Unload();

            m_WorldState = WorldState.Loading;
            m_Config = worldConfig;

            m_SceneInstance = worldConfig.mainScene.LoadSceneAsync(LoadSceneMode.Additive, false);
            await m_SceneInstance.ToUniTask(cancellationToken: cancellationToken);

            await m_SceneInstance.Result.ActivateAsync();
            SceneManager.SetActiveScene(m_SceneInstance.Result.Scene);

            foreach (GameObject root in m_SceneInstance.Result.Scene.GetRootGameObjects()) {
                if (root.TryGetComponent(out IWorldContext worldContext)) {
                    Assert.IsNull(Context,
                                  "WorldContext is already set. There should be only one WorldContext in the scene.");
                    Context = worldContext;
                }

                foreach (Actor actor in root.GetComponentsInChildren<Actor>()) {
                    RR.Network.Actors.RegisterInSceneActor(actor);
                }
            }

            m_WorldState = WorldState.Loaded;
        }

        public void Unload() {
            if (m_WorldState == WorldState.Unloaded) {
                return;
            }

            if (RR.Network != null) {
                RR.Network.Actors.CleanUp();
            }
            
            if (m_SceneInstance.IsValid()) {
                m_SceneInstance.Release();
                m_SceneInstance = default;
            }

            m_WorldState = WorldState.Unloaded;
            Context = null;
        }
    }
}