using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Unity.Services.Authentication;
using Unity.Services.CloudSave;
using Unity.Services.Core;
using UnityEngine;

public class CloudSaveClient : ISaveClient
{
    public async Task Initialise()
    {
        await UnityServices.InitializeAsync();
        await AuthenticationService.Instance.SignInAnonymouslyAsync();
        Client = CloudSaveService.Instance.Data;
    }
    Unity.Services.CloudSave.Internal.IDataService Client;

    #region Save

    public async Task Save(string Key, object Value)
    {
        var Data = new Dictionary<string, object> { { Key, Value } };
        await Call(Client.Player.SaveAsync(Data));
    }

    public async Task Save(params (string Key, object Value)[] Values)
    {
        var Data = Values.ToDictionary(Item => Item.Key, Item => Item.Value);
        await Call(Client.Player.SaveAsync(Data));
    }

    #endregion

    #region Load

    public async Task<T> Load<T>(string Key)
    {
        var Query = await Call(Client.Player.LoadAsync(new HashSet<string> { Key }));
        return Query.TryGetValue(Key, out var Item) ? Deserialize<T>(Item.Value.GetAsString()) : default;
    }

    public async Task<IEnumerable<T>> Load<T>(params string[] Keys)
    {
        var Query = await Call(Client.Player.LoadAsync(Keys.ToHashSet()));

        return Keys.Select(Key =>
        {
            if (Query.TryGetValue(Key, out var Item) && Item.Value != null)
            {
                return Deserialize<T>(Item.Value.GetAsString());
            }

            return default;
        });
    }

    #endregion

    #region Delete

    public async Task Delete(string Key)
    {
        await Call(Client.Player.DeleteAsync(Key, new Unity.Services.CloudSave.Models.Data.Player.DeleteOptions()));
    }

    public async Task DeleteAll()
    {
        var ItemKeys = await Call(Client.Player.ListAllKeysAsync());
        var Keys = ItemKeys.Select(ItemKey => ItemKey.Key).ToList();
        var Tasks = Keys.Select(Key => Client.Player.DeleteAsync(Key, new Unity.Services.CloudSave.Models.Data.Player.DeleteOptions())).ToList();
        await Call(Task.WhenAll(Tasks));
    }

    #endregion

    private static T Deserialize<T>(string Input)
    {
        if (typeof(T) == typeof(string)) return (T)(object)Input;
        return JsonConvert.DeserializeObject<T>(Input);
    }

    public async Task<List<string>> GetKeys()
    {
        var ItemKeys = await Call(Client.Player.ListAllKeysAsync());
        return ItemKeys.Select(ItemKey => ItemKey.Key).ToList();
    }

    #region Error Handling

    static async Task Call(Task Action)
    {
        try
        {
            await Action;
        }
        catch (CloudSaveValidationException Exception)
        {
            Debug.LogError(Exception);
        }
        catch (CloudSaveRateLimitedException Exception)
        {
            Debug.LogError(Exception);
        }
        catch (CloudSaveException Exception)
        {
            Debug.LogError(Exception);
        }
    }

    static async Task<T> Call<T>(Task<T> Action)
    {
        try
        {
            return await Action;
        }
        catch (CloudSaveValidationException Exception)
        {
            Debug.LogError(Exception);
        }
        catch (CloudSaveRateLimitedException Exception)
        {
            Debug.LogError(Exception);
        }
        catch (CloudSaveException Exception)
        {
            Debug.LogError(Exception);
        }

        return default;
    }

    #endregion
}

#region Interface

public interface ISaveClient
{
    Task Initialise();
    Task Save(string Key, object Value);

    Task Save(params (string Key, object Value)[] Values);

    Task<T> Load<T>(string Key);

    Task<IEnumerable<T>> Load<T>(params string[] Keys);

    Task Delete(string Key);

    Task DeleteAll();

    Task<List<string>> GetKeys();
}

#endregion