using System;
using Intrepid.Diagnostics;
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 Slot", menuName = "GON/Services/Persistence Game Slot")]
    public class GameSlotPersistenceService : AbstractPersistenceService<GameSlotSaveFileDTO>
    {
        public static readonly SavingCallbacksAggregator<GameSlotSaveFileDTO> Events = new(
            gameData =>
            {
                StorageAssembler.Instance.TakeSnapshot(gameData.slot);
                Proxy.Instance?.EventBus.Raise(new SavingGameDataBeginEvent());
            },
            isOk => Proxy.Instance?.EventBus.Raise(new SavingGameDataEndEvent { IsOk = isOk }));
        
        [GroupInjections, SerializeField] private ConfigurationPersistenceService configurationPersistenceService;

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

        [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 GameSlotDTO CurrentGameSlot => currentGameSlotSaveFile?.slot;

        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(ButtonSizes.Medium, ButtonStyle.FoldoutButton), PropertyOrder(-10)]
        public void SaveCurrentGameDataQuietly()
        {
            SaveCurrentGameData();
        }

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

        [GroupDebug, Button(ButtonSizes.Medium, ButtonStyle.FoldoutButton), PropertyOrder(-10)]
        public GameSlotSaveFileDTO CreateEmptyGameDataAsCurrent(GameDataSlotType gameSlotType = GameDataSlotType.Slot01)
        {
            var gameData = GameSlotDTO.WithInitialValues();
            currentGameSlotSaveFile = new GameSlotSaveFileDTO
            {
                fileId = BuildSaveSlotIdFrom(gameSlotType),
                slot = gameData
            };
            CurrentGameDataSlotTypeLoaded = gameSlotType;
            return currentGameSlotSaveFile;
        }

        public GameSlotSaveFileDTO LoadGame(GameDataSlotType gameSlotType = GameDataSlotType.Slot01)
        {
            return Load(BuildSaveSlotIdFrom(gameSlotType));
        }

        private void SetAsCurrent(GameDataSlotType gameSlotType, GameSlotSaveFileDTO gameSlotSaveSlotSave)
        {
            CurrentGameDataSlotTypeLoaded = gameSlotType;
            currentGameSlotSaveFile = gameSlotSaveSlotSave;
        }

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

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

        [GroupDebug, Button(ButtonSizes.Medium, ButtonStyle.FoldoutButton), PropertyOrder(-10)]
        public void ClearGameData()
        {
            currentGameSlotSaveFile = null;
            CurrentGameDataSlotTypeLoaded = GameDataSlotType.None;
        }
    }
}