using Intrepid.Diagnostics;
using Intrepid.Gameplay.Journey.Runtime.Events;
using Intrepid.Gameplay.Journey.Runtime.Loading;
using Intrepid.Gameplay.Journey.Runtime.Messages;
using Intrepid.Gameplay.Journey.Runtime.Resolvers;
using Intrepid.Gameplay.Journey.Runtime.State;
using Intrepid.Gameplay.Journey.Runtime.Transition;
using Intrepid.SimpleEvents;
using Intrepid.Theatre.BackStage;
using Intrepid.Utilities;
using UnityEngine;

namespace Intrepid.Gameplay.Journey.Runtime
{
    public enum JourneyRuntimeBootMode
    {
        FromSave,
        LoadTestStage,
        UseOpenedTestStage
    }

    public class JourneyRuntimeManager : SingletonEventListener<JourneyRuntimeManager>
    {
#if UNITY_EDITOR
        [GroupDebug, SerializeField] 
        private JourneyRuntimeBootMode bootMode = JourneyRuntimeBootMode.FromSave;
#endif

        [GroupInjections, SerializeField] private BackStageService backStageService;
        [GroupInjections, SerializeField] private JourneyBootDirector bootDirector;
        [GroupInjections, SerializeField] private JourneyTransitionDirector transitionDirector;
        [GroupInjections, SerializeField] private JourneyStageLoader stageLoader;
        [GroupInjections, SerializeField] private JourneySocketTravelResolver socketTravelResolver;

        private JourneyRuntimeState RuntimeState { get; } = new();

        public JourneyRuntimePhase Phase => RuntimeState.Phase;
        public bool IsBooted => RuntimeState.IsBooted;
        public bool IsTransitionRunning => RuntimeState.IsTransitionRunning;
        public StageTag CurrentStageTag => RuntimeState.CurrentStageTag;

        private JourneyRuntimeBootMode BootMode
        {
            get
            {
#if UNITY_EDITOR
                return bootMode;
#else
                return JourneyRuntimeBootMode.FromSave;
#endif
            }
        }

        protected override void InitializeVariables()
        {
            SingletonInstantiation(false, _ =>
            {
                stageLoader.LoadMachine();
            });
        }

        protected override void OnBootstrap()
        {
            RuntimeState.BeginBoot();

            bootDirector.StartBoot(
                BootMode,
                OnBootFinished,
                OnBootFailed);
        }

        protected override void OnTearDown()
        {
            stageLoader.TearDown();
            RuntimeState.Reset();
        }

        protected override void Subscriptions()
        {
            Subscribe<StageSocketChangeEnterEvent>(OnStageSocketChangeEnterEvent);
        }

        private void Update()
        {
            backStageService.UpdateCurtains();
        }

        private void OnStageSocketChangeEnterEvent(StageSocketChangeEnterEvent e)
        {
            if (!CanAcceptTravelRequest())
            {
                return;
            }

            if (!socketTravelResolver.TryCreateSocketTravelRequest(
                    RuntimeState.CurrentStageTag,
                    e.StageTag,
                    e.EndpointId,
                    out var request))
            {
                return;
            }

            Travel(request);
        }

        private bool CanAcceptTravelRequest()
        {
            if (RuntimeState.CanAcceptTravelRequest(out var reason))
            {
                return true;
            }

            DLogger.LogWarn(
                $"rejected travel request because {reason}. State: {RuntimeState}",
                ctx: this);

            return false;
        }

        private void Travel(JourneyTravelRequest request)
        {
            if (!RuntimeState.TryBeginTransition(request, out var reason))
            {
                DLogger.LogWarn(
                    $"rejected travel because {reason}. " +
                    $"Request: {request}. State: {RuntimeState}",
                    ctx: this);

                return;
            }

            DLogger.LogDebug(
                $"travelling: {request}. State: {RuntimeState}",
                ctx: this);

            var started = transitionDirector.StartTravel(
                request,
                OnTravelFinished,
                OnTravelFailed);

            if (started)
            {
                return;
            }

            if (RuntimeState.IsTransitionRunning)
            {
                RuntimeState.FailTransition();
            }

            DLogger.LogWarn(
                $"failed to start transition. Request: {request}. State: {RuntimeState}",
                ctx: this);
        }

        private void OnBootFinished(StageTag stageTag)
        {
            RuntimeState.CompleteBoot(stageTag);

            DLogger.LogDebug(
                $"boot finished: {stageTag}. State: {RuntimeState}",
                ctx: this);
        }

        private void OnBootFailed()
        {
            RuntimeState.FailBoot();

            DLogger.LogWarn(
                $"boot failed. State: {RuntimeState}",
                ctx: this);
        }

        private void OnTravelFinished(JourneyTravelRequest request)
        {
            RuntimeState.CompleteTransition(request);

            DLogger.LogDebug(
                $"travel finished: {request}. State: {RuntimeState}",
                ctx: this);
        }

        private void OnTravelFailed(JourneyTravelRequest request)
        {
            RuntimeState.FailTransition();

            DLogger.LogWarn(
                $"travel failed: {request}. State: {RuntimeState}",
                ctx: this);
        }
    }
}