using System;
using System.Collections;
using Intrepid.Core.Camera;
using Intrepid.Diagnostics;
using Intrepid.GameManagers;
using Intrepid.Gameplay.Entities.Player.ActionBelt.Events;
using Intrepid.Gameplay.Entities.Player.Tags;
using Intrepid.Gameplay.Journey.Messages;
using Intrepid.Gameplay.Journey.Messages.Events;
using Intrepid.Integration.Input;
using Intrepid.SimpleEvents;
using Intrepid.Theatre.BackStage;
using Intrepid.Theatre.Curtain;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
using UnityEngine;

namespace Intrepid.Gameplay.Journey
{
    public class JourneyManager : SingletonEventListener<JourneyManager>, IBackStageProcessor
    {
        private const string Id = "JourneyManager";
        
        [GroupConfigurations, SerializeField] private CurtainTypeTag curtainType;
        [GroupInjections, SerializeField] private ActionBeltManagerTag actionBeltManager;
        [GroupInjections, SerializeField] private BackStageService backStageService;
#if UNITY_EDITOR
        [GroupDebug, SerializeField, ToggleLeft] private bool isRunningTest;
        [GroupDebug, SerializeField, ShowIf("IsRunningTest")] private StageTag stageToTest;
        [GroupDebug, SerializeField, ShowIf("IsRunningTest")] private Transform landingStart;
#endif
        
        private StageStateMachine StageStateMachine { get; } = new();
        private CurtainTypeTag CurtainType => curtainType;
        private BackStageService BackStageService => backStageService;
#if UNITY_EDITOR
        private bool IsRunningTest => isRunningTest;
#endif
        private StageTag StageToChange { get; set; }
        private IBackStageWindowExecution CallbackProcessors { get; set; }
        private string DestinyEndpoint { get; set; }
        private InputSchema LastInputSchema { get; set; } = InputSchema.None;
        public ICurtainProgress OpenCurtainProgress { get; set; }
        
        public bool IsProcessFinished => StageStateMachine.IsLoadingFinished && !CameraSystem.Instance.IsCameraBlending;
        
        protected override void InitializeVariables()
        {
            SingletonInstantiation(false, manager =>
            {
                manager.StageStateMachine.LoadMachine(JourneyService.Instance.StageTags);
            });
        }

        protected override void OnBootstrap()
        {
#if UNITY_EDITOR
            if (IsRunningTest && stageToTest.IsNotNull())
            {
                LoadJourneyForTesting(stageToTest, landingStart);
            }
#endif
        }

        protected override void Subscriptions()
        {
        }
        
        private void Update()
        {
            BackStageService.UpdateCurtains();
            if (OpenCurtainProgress?.IsFinished() ?? false)
            {
                OpenCurtainProgress = null;
                AfterOpened();
            }
        }

        protected override void OnTearDown()
        {
            StageStateMachine.TearDown();
        }
        
        private void LoadJourney(LoadJourneyRequest request)
        {
            if (request is null)
            {
                DLogger.LogError("load journey request should not be null");
                return;
            }
            if (request.StageTag.IsNull() || string.IsNullOrWhiteSpace(request.StageTag.Code))
            {
                DLogger.LogError("stage tag/code should not be null or empty");
                return;
            }
            
            StageStateMachine.InitMachine(request.StageTag);
        }

        [GroupDebug, Button(ButtonSizes.Large, ButtonStyle.FoldoutButton)]
        private void LoadJourneyForTesting(StageTag stage, Transform landing)
        {
            LoadJourney(new LoadJourneyRequest
            {
                StageTag = stage
            });
            StartCoroutine(OpenAfterNSecs(landing.IsNull() ? landingStart : landing));
        }

        private IEnumerator OpenAfterNSecs(Transform landing, float nSecs = 1f)
        {
            yield return new WaitForSeconds(nSecs);
            Proxy.Instance.EventBus.Raise(new StageDispatchDeploymentPointEvent
            {
                DeploymentPoint = landing.position,
            });
            OpenCurtainProgress = Proxy.Instance.CurtainStateMachine.RaiseCommand(CurtainCommandType.Open);
        }

        [GroupDebug, Button(ButtonSizes.Large, ButtonStyle.FoldoutButton)]
        private void ChangeStageForTesting(StageTag stageTag, string destinyEndpoint)
        {
            ChangeStageTo(stageTag, destinyEndpoint);
        }
        
        public void ChangeStageTo(
            StageTag stageTag,
            string destinyEndpoint,
            IBackStageWindowExecution callbacks = null)
        {
#if UNITY_EDITOR
            if (IsRunningTest)
            {
                DLogger.LogDebug("that is a test and has a fake journey manager. STAGE WILL NOT BE CHANGED.");
                return;
            }
#endif
            if (stageTag.IsNull() || string.IsNullOrEmpty(stageTag.Code))
            {
                throw new ArgumentException("stage tag cannot be null or empty");
            }
            if (StageStateMachine.CurrentStageTag == stageTag) return;
            DestinyEndpoint = destinyEndpoint;
            if (BackStageService.KickProcess(CurtainType, Id, this))
            {
                StageToChange = stageTag;
            }
            CallbackProcessors = callbacks;
        }

        public void AfterOpened()
        {
            DestinyEndpoint = null;
            CallbackProcessors?.AfterOpened();
            Proxy.Instance.EventBus.Raise(new ChangeStageEndEvent());
            var lastInputSchema = LastInputSchema == InputSchema.None
                ? InputSchema.Sentinel
                : LastInputSchema;
            InputService.Instance.ChangeInputSchema(lastInputSchema);
            Proxy.Instance.EventBus.Raise(new ActionBeltEnableEvent
            {
                ActionBeltManger = actionBeltManager,
            });
            LastInputSchema = InputSchema.None;
        }

        public void BeforeOpening()
        {
            CallbackProcessors?.BeforeOpening();
            Proxy.Instance.EventBus.Raise(new ChangeStageLandingOnStageStepEvent
            {
                DestinyEndpoint = DestinyEndpoint
            });
        }

        public void AfterClosed()
        {
            Proxy.Instance.EventBus.Raise(new ChangeStageStartEvent());
            StageStateMachine.ChangeState(StageToChange);
            CallbackProcessors?.AfterClosed();
        }

        public void BeforeClosing()
        {
            LastInputSchema = InputService.Instance.CurrentInputSchema;
            InputService.Instance.ChangeInputSchema(InputSchema.None);
            CallbackProcessors?.BeforeClosing();
            Proxy.Instance.EventBus.Raise(new ChangeStageTouchedEvent());
            Proxy.Instance.EventBus.Raise(new ActionBeltDisableEvent
            {
                ActionBeltManger = actionBeltManager,
            });
        }
    }
}