using System;
using Intrepid.GameManagers;
using Intrepid.Integration.Persistence.DTOs;
using Intrepid.Integration.Persistence.Events;
using Intrepid.SaveSystem;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
using UnityEngine;

namespace Intrepid.Integration.Persistence
{
    [Serializable]
    [CreateAssetMenu(fileName = "Persistence Game Data", menuName = "Intrepid/Save System/Persistence Game Data")]
    public class GameDataPersistenceService : AbstractPersistenceService<GameDataSaveFileDTO>
    {
        public static readonly SavingCallbacksAggregator<GameDataSaveFileDTO> Events = new(
            gameData => Proxy.Instance?.EventBus.Raise(new SavingGameDataBeginEvent { GameData = gameData }),
            isOk => Proxy.Instance?.EventBus.Raise(new SavingGameDataEndEvent { IsOk = isOk }));
        
        [GroupInjections, SerializeField]
        private ConfigurationPersistenceService configurationPersistenceService;

        [GroupDebug, ReadOnly, SerializeField, InlineProperty, HideLabel, Title("Current Slot"), PropertySpace]
        private GameDataSaveFileDTO currentGameDataSaveFile;

        [GroupDebug, ReadOnly, ShowInInspector, PropertyOrder(-9), PropertySpace, LabelText("Game Slot")]
        public GameDataSlotType CurrentGameDataSlotTypeLoaded
        {
            get => !configurationPersistenceService.IsNull()
                ? configurationPersistenceService.Configuration?.lastGameSlotType ?? GameDataSlotType.None
                : GameDataSlotType.None;
            private set
            {
                if (configurationPersistenceService.IsNull()) return;
                configurationPersistenceService.Configuration.lastGameSlotType = value;
                configurationPersistenceService.SaveConfigurationQuietly();
            }
        }

        public GameDataDTO CurrentGameData => currentGameDataSaveFile.data;

        private bool ExistsGameSlot(GameDataSlotType gameSlotType)
        {
            return Exists(BuildSaveSlotIdFrom(gameSlotType));
        }

        public static string BuildSaveSlotIdFrom(GameDataSlotType gameSlotType)
        {
            return $"game-slot-{Stringify(gameSlotType)}";
        }

        private static string Stringify(GameDataSlotType gameSlotType)
        {
            return gameSlotType switch
            {
                GameDataSlotType.Slot01 => "slot-01",
                GameDataSlotType.Slot02 => "slot-02",
                GameDataSlotType.Slot03 => "slot-03",
                _ => throw new ArgumentOutOfRangeException(nameof(gameSlotType), gameSlotType, null)
            };
        }

        public bool Exists(GameDataSlotType gameSlotType)
        {
            return Exists(BuildSaveSlotIdFrom(gameSlotType));
        }

        public void Delete(GameDataSlotType gameSlotType)
        {
            Delete(BuildSaveSlotIdFrom(gameSlotType));
        }

        [GroupDebug]
        [Button]
        [PropertyOrder(-10)]
        public void SaveCurrentGameDataQuietly()
        {
            SaveCurrentGameData();
        }

        public async void SaveCurrentGameData(SavingCallbacksAggregator<GameDataSaveFileDTO> savingCallbacks = null)
        {
            savingCallbacks ??= SavingCallbacksAggregator<GameDataSaveFileDTO>.Empty();
            savingCallbacks += Events;
            await Save(currentGameDataSaveFile, savingCallbacks);
        }

        [GroupDebug]
        [Button(ButtonStyle.FoldoutButton)]
        [PropertyOrder(-10)]
        public GameDataSaveFileDTO CreateEmptyGameDataAsCurrent(GameDataSlotType gameSlotType)
        {
            var gameData = GameDataDTO.WithInitialValues();
            currentGameDataSaveFile = new GameDataSaveFileDTO
            {
                fileId = BuildSaveSlotIdFrom(gameSlotType),
                data = gameData
            };
            CurrentGameDataSlotTypeLoaded = gameSlotType;
            return currentGameDataSaveFile;
        }

        public GameDataSaveFileDTO LoadGame(GameDataSlotType gameSlotType)
        {
            return Load(BuildSaveSlotIdFrom(gameSlotType));
        }

        public void SetAsCurrent(GameDataSlotType gameSlotType, GameDataSaveFileDTO gameDataSaveSlot)
        {
            CurrentGameDataSlotTypeLoaded = gameSlotType;
            currentGameDataSaveFile = gameDataSaveSlot;
        }

        [GroupDebug]
        [Button(ButtonStyle.FoldoutButton)]
        [PropertyOrder(-10)]
        private void LoadGameDataAsCurrent(GameDataSlotType gameSlotType)
        {
            if (ExistsGameSlot(gameSlotType))
            {
                SetAsCurrent(gameSlotType, LoadGame(gameSlotType));
                return;
            }

            ClearGameData();
            Dbg.Always.LogError($"no game slot available to load: {BuildSaveSlotIdFrom(gameSlotType)}");
        }

        [GroupDebug]
        [Button]
        [PropertyOrder(-10)]
        private void ClearGameData()
        {
            currentGameDataSaveFile = null;
            CurrentGameDataSlotTypeLoaded = GameDataSlotType.None;
        }
    }
}