using System.Collections.Generic;
using Jovian.Calendar;
using Jovian.EncounterSystem;
using Jovian.PopupSystem;
using Jovian.PopupSystem.UI;
using Jovian.SaveSystem;
using Jovian.ZoneSystem;
using Nox.Core;
using Nox.Platform;
using Nox.Game.UI;
using Nox.UI;
using System;
using UnityEngine;
using UnityEngine.AddressableAssets;
using ZLinq;
using Object = UnityEngine.Object;
using PlayMode = Nox.Core.PlayMode;

namespace Nox.Game {
    [Serializable]
    public class AdventureData {
        public bool isPartyMoving;
        public bool isEncounterActive;
        public int currentDay = 0;
        public int suppliesAvailable = -1;
        public float currentTime = -1f;
        public DayPhase currentDayPhase = DayPhase.Morning;
        public List<string> completedEncounterIds = new List<string>();
    }

    public class AdventurePlayMode : IPlayMode {
        private readonly PlatformSettings platformSettings;
        private readonly PlayModeSettings bootstrapSettings;
        private readonly GameDataState gameDataState;
        private readonly ISaveSystem saveSystem;
        private readonly PartyDefinition partyDefinition;
        private readonly AdventureSettings adventureSettings;
        private AdventureData adventureData;
        private AdventureModePrefabs scenePrefabs;
        private ICameraController cameraController;
        private MapReference mapRef;
        private PartyMovementHandler partyMovementHandler;
        private PartyReference partyRef;
        private MapLocationsReference mapLocationsReference;
        private InputSystem_Actions inputActions;
        private AdventureView adventureView;
        private ZoneSystem zoneSystem;
        private GuiReferences guiReferences;
        private TimeHandler timeHandler;
        private PartyInventoryHandler partyInventoryHandler;
        private PartyGuiView partyGuiView;
        private IPopupSystem popupSystem;
        private EncounterRegistry encounterRegistry;
        private EncounterHandler encounterHandler;
        private EncounterPrefabs encounterPrefabs;

        public AdventurePlayMode(
            PlatformSettings platformSettings,
            PartyDefinition partyDefinition,
            PlayModeSettings bootstrapSettings,
            GameDataState gameDataState,
            ISaveSystem saveSystem,
            AdventureSettings adventureSettings,
            AdventureData adventureData) {
            this.platformSettings = platformSettings;
            this.partyDefinition = partyDefinition;
            this.bootstrapSettings = bootstrapSettings;
            this.gameDataState = gameDataState;
            this.saveSystem = saveSystem;
            this.adventureSettings = adventureSettings;
            this.adventureData = adventureData;
        }

        public bool IsGameModeInitialized { get; private set; }

        public void EnterPlayMode() {
            inputActions = platformSettings.inputSettings.inputActions;
            if(IsGameModeInitialized) {
                inputActions.Player.Enable();
                inputActions.UI.PauseMenu.Enable();
                partyMovementHandler.ConsumeNextClick();
                return;
            }
            Addressables.LoadSceneAsync(bootstrapSettings.gameModeData.AsValueEnumerable().FirstOrDefault(g => g.playMode == PlayMode.Adventure)?.scene)
                .WaitForCompletion().ActivateAsync().completed += InitializeGameMode;
        }

        private void InitializeGameMode(AsyncOperation obj) {
            inputActions.Player.Enable();
            inputActions.UI.PauseMenu.Enable();
            Debug.Log("Entering Adventure Play Mode");
            if(partyDefinition == null) {
                var restoreResult = NoxSaveData.RestoreSavedData(saveSystem, gameDataState, ref adventureData);
                if(restoreResult == null) {
                    Debug.LogError("AdventurePlayMode: no save data found for auto-recovery. Party will be null.");
                }
            }

            encounterRegistry ??= Addressables.LoadAssetAsync<EncounterRegistry>("EncounterRegistry").WaitForCompletion();
            scenePrefabs ??= Addressables.LoadAssetAsync<AdventureModePrefabs>("AdventureMapPrefabs").WaitForCompletion();
            encounterPrefabs = Addressables.LoadAssetAsync<EncounterPrefabs>("EncounterPrefabs").WaitForCompletion();
            mapRef ??= Object.FindFirstObjectByType<MapReference>();
            partyRef ??= Object.FindFirstObjectByType<PartyReference>();
            if(partyRef && gameDataState.savedPartyPosition.HasValue) {
                partyRef.transform.position = gameDataState.savedPartyPosition.Value;
            }
            mapLocationsReference ??= Object.FindFirstObjectByType<MapLocationsReference>();
            if(!mapRef) {
                mapRef ??= Object.Instantiate(scenePrefabs.mapReferencePrefab);
            }
            cameraController ??= new CameraController(platformSettings, mapRef);
            cameraController.Initialize();

            if(adventureData.suppliesAvailable == -1) {
                adventureData.suppliesAvailable = adventureSettings.maxSupplies;
            }
            if(Mathf.Approximately(adventureData.currentTime, -1f)) {
                adventureData.currentTime = 0.25f;
            }

            partyInventoryHandler ??= new PartyInventoryHandler(adventureData, adventureSettings);
            partyInventoryHandler.Initialize();

         var calendarSettings = Addressables.LoadAssetAsync<CalendarSettings>("CalendarSettings").WaitForCompletion();
            var worldClock = new WorldClock(calendarSettings);
            var totalMinutes = adventureData.currentDay * calendarSettings.MinutesPerDay
                + (int)(adventureData.currentTime * calendarSettings.MinutesPerDay);
            worldClock.AdvanceMinutes(totalMinutes);
            timeHandler ??= new TimeHandler(adventureSettings, adventureData, worldClock);

            zoneSystem ??= new ZoneSystem(mapRef.zonesObjectHolder);
            encounterHandler = new EncounterHandler(zoneSystem, encounterRegistry, encounterPrefabs, adventureData);
            partyMovementHandler ??= new PartyMovementHandler(
                partyRef,
                cameraController,
                mapLocationsReference,
                platformSettings.inputSettings,
                encounterHandler,
                adventureData,
                adventureSettings);
            partyMovementHandler.Initialize();

            guiReferences ??= Object.FindFirstObjectByType<GuiReferences>();
            adventureView ??= new AdventureView(gameDataState, guiReferences, inputActions, adventureData, adventureSettings, worldClock);
            adventureView.Initialize();

            if(partyGuiView == null && guiReferences.partyMemberSlotPrefab != null) {
                var portraitsHolder = Addressables.LoadAssetAsync<PortraitsHolder>("PortraitsHolder").WaitForCompletion();

                if(popupSystem == null) {
                    var popupSettings = Addressables.LoadAssetAsync<PopupSettings>("PopupSettings").WaitForCompletion();
                    var popupViewPrefab = Addressables.LoadAssetAsync<GameObject>("PopupReferencePrefab").WaitForCompletion().GetComponent<PopupReference>();
                    var guiCanvas = guiReferences.GetComponentInParent<Canvas>().transform;
                    popupSystem = new PopupSystem(popupSettings, popupViewPrefab, guiCanvas);
                    popupSystem.RegisterCategory(PopupCategory.Character, priority: 10);
                }

                partyGuiView = new PartyGuiView(guiReferences.portraitsContainer, guiReferences.partyMemberSlotPrefab, portraitsHolder, popupSystem);
                partyGuiView.Initialize(partyDefinition);
            }

            IsGameModeInitialized = true;
        }

        public void Tick() {
            if(!IsGameModeInitialized || gameDataState.ActiveParty == null) {
                return;
            }

            timeHandler.Tick();
            partyInventoryHandler.Tick();
            partyMovementHandler.Tick();
            encounterHandler.Tick();
            adventureView.Tick();
            partyGuiView?.Tick();
            popupSystem?.Tick(Time.deltaTime);

            if(inputActions.UI.PauseMenu.WasPerformedThisFrame()) {
                gameDataState.ChangePlayMode(PlayMode.PauseMenu);
            }
        }
        public void LateTick() {
            if(!IsGameModeInitialized) {
                return;
            }
            cameraController.Tick();
        }
        public NoxSavedDataSet CaptureNoxSaveData() {
            return new NoxSavedDataSet {
                activePlayMode = PlayMode.Adventure,
                activeParty = partyDefinition,
                partyPosition = partyRef ? SerializableVector3.FromVector3(partyRef.transform.position) : SerializableVector3.Zero,
                adventureData = this.adventureData
            };
        }

        public void ExitGameMode() {
            inputActions.Player.Disable();
            inputActions.UI.PauseMenu.Disable();
        }

        public void Dispose() {
            partyGuiView?.Dispose();
            popupSystem?.Dispose();
            cameraController?.Dispose();
            partyMovementHandler?.Dispose();
            encounterHandler?.Dispose();
            encounterRegistry = null;
            Resources.UnloadUnusedAssets();
        }
    }

}
