﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

using PlayFab;
using PlayFab.ClientModels;

namespace Bayat.SaveSystem.Storage
{

    public class PlayFabUserDataStorage : StorageBase
    {

        public const string DefaultEntityType = "title_player_account";

        protected bool useBase64 = true;
        protected string entityId;
        protected string entityType;

        public PlayFabUserDataStorage(string encodingName, bool useBase64) : base(encodingName)
        {
            this.useBase64 = useBase64;
        }

        public override Task<IStorageStream> GetWriteStream(string identifier)
        {
            return Task.FromResult<IStorageStream>(new PlayFabUserDataStorageStream(identifier, new MemoryStream()));
        }

        protected override Task CommitWriteStreamInternal(IStorageStream stream)
        {
            PlayFabUserDataStorageStream playFabStream = (PlayFabUserDataStorageStream)stream;
            UpdateUserDataRequest request = new UpdateUserDataRequest();
            string data = null;
            if (useBase64)
            {
                data = Convert.ToBase64String(playFabStream.MemoryStream.ToArray());
            }
            else
            {
                data = this.TextEncoding.GetString(playFabStream.MemoryStream.ToArray());
            }
            request.Data = new Dictionary<string, string>()
            {
                { stream.Identifier, data }
            };
            TaskCompletionSource<UpdateUserDataResult> taskCompletionSource = new TaskCompletionSource<UpdateUserDataResult>();
            PlayFabClientAPI.UpdateUserData(request, result =>
            {
                taskCompletionSource.SetResult(result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override Task<IStorageStream> GetReadStream(string identifier)
        {
            TaskCompletionSource<IStorageStream> taskCompletionSource = new TaskCompletionSource<IStorageStream>();
            GetUserDataRequest request = new GetUserDataRequest();
            request.Keys = new List<string>() { identifier };
            PlayFabClientAPI.GetUserData(request, result =>
            {
                MemoryStream memoryStream = null;
                if (useBase64)
                {
                    memoryStream = new MemoryStream(Convert.FromBase64String(result.Data[identifier].Value));
                }
                else
                {
                    memoryStream = new MemoryStream(this.TextEncoding.GetBytes(result.Data[identifier].Value));
                }
                taskCompletionSource.SetResult(new PlayFabUserDataStorageStream(identifier, memoryStream));
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        protected override Task WriteAllTextInternal(string identifier, string data)
        {
            UpdateUserDataRequest request = new UpdateUserDataRequest();
            request.Data = new Dictionary<string, string>()
            {
                { identifier, data }
            };
            TaskCompletionSource<UpdateUserDataResult> taskCompletionSource = new TaskCompletionSource<UpdateUserDataResult>();
            PlayFabClientAPI.UpdateUserData(request, result =>
            {
                taskCompletionSource.SetResult(result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override Task<string> ReadAllText(string identifier)
        {
            TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
            GetUserDataRequest request = new GetUserDataRequest();
            request.Keys = new List<string>() { identifier };
            PlayFabClientAPI.GetUserData(request, result =>
            {
                taskCompletionSource.SetResult(result.Data[identifier].Value);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        protected override Task WriteAllBytesInternal(string identifier, byte[] data)
        {
            UpdateUserDataRequest request = new UpdateUserDataRequest();
            string stringData = null;
            if (useBase64)
            {
                stringData = Convert.ToBase64String(data);
            }
            else
            {
                stringData = this.TextEncoding.GetString(data);
            }
            request.Data = new Dictionary<string, string>()
            {
                { identifier, stringData }
            };
            TaskCompletionSource<UpdateUserDataResult> taskCompletionSource = new TaskCompletionSource<UpdateUserDataResult>();
            PlayFabClientAPI.UpdateUserData(request, result =>
            {
                taskCompletionSource.SetResult(result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override Task<byte[]> ReadAllBytes(string identifier)
        {
            TaskCompletionSource<byte[]> taskCompletionSource = new TaskCompletionSource<byte[]>();
            GetUserDataRequest request = new GetUserDataRequest();
            request.Keys = new List<string>() { identifier };
            PlayFabClientAPI.GetUserData(request, result =>
            {
                byte[] data = null;
                if (useBase64)
                {
                    data = Convert.FromBase64String(result.Data[identifier].Value);
                }
                else
                {
                    data = this.TextEncoding.GetBytes(result.Data[identifier].Value);
                }
                taskCompletionSource.SetResult(data);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override async Task<StorageClearOperationResult> Clear()
        {
            TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
            List<string> removedKeys = new List<string>();
            bool succeed = true;
            GetUserDataRequest request = new GetUserDataRequest();
            PlayFabClientAPI.GetUserData(request, async result =>
            {
                List<string> keysToRemove = new List<string>(result.Data.Keys);
                removedKeys = new List<string>(result.Data.Keys);
                UpdateUserDataRequest deleteRequest = new UpdateUserDataRequest();
                deleteRequest.KeysToRemove = keysToRemove;
                TaskCompletionSource<bool> deleteTaskCompletionSource = new TaskCompletionSource<bool>();
                PlayFabClientAPI.UpdateUserData(deleteRequest, deleteResult =>
                {
                    taskCompletionSource.SetResult(true);
                    succeed = true;
                }, error =>
                {
                    Debug.LogError(error.ErrorMessage);
                    taskCompletionSource.SetResult(false);
                    succeed = false;
                });
                await deleteTaskCompletionSource.Task;
                taskCompletionSource.SetResult(deleteTaskCompletionSource.Task.Result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(false);
            });
            await taskCompletionSource.Task;
            return new StorageClearOperationResult(succeed, removedKeys.ToArray());
        }

        protected override async Task<StorageDeleteOperationResult> DeleteInternal(string identifier)
        {
            UpdateUserDataRequest request = new UpdateUserDataRequest();
            bool succeed = true;
            request.KeysToRemove = new List<string>() { identifier };
            TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
            PlayFabClientAPI.UpdateUserData(request, result =>
            {
                taskCompletionSource.SetResult(true);
                succeed = true;
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(false);
                succeed = false;
            });
            await taskCompletionSource.Task;
            return new StorageDeleteOperationResult(succeed);
        }

        public override Task<bool> Exists(string identifier)
        {
            TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
            GetUserDataRequest request = new GetUserDataRequest();
            request.Keys = new List<string>() { identifier };
            PlayFabClientAPI.GetUserData(request, result =>
            {
                taskCompletionSource.SetResult(result.Data.ContainsKey(identifier));
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(false);
            });
            return taskCompletionSource.Task;
        }

        protected override async Task<StorageMoveOperationResult> MoveInternal(string oldIdentifier, string newIdentifier, bool replace)
        {
            string data = await ReadAllText(oldIdentifier);
            await WriteAllText(newIdentifier, data);
            StorageDeleteOperationResult deleteOp = await Delete(oldIdentifier);
            return new StorageMoveOperationResult(deleteOp.Succeed, newIdentifier);
        }

        protected override async Task<StorageCopyOperationResult> CopyInternal(string fromIdentifier, string toIdentifier, bool replace)
        {
            string data = await ReadAllText(fromIdentifier);
            await WriteAllText(toIdentifier, data);
            return new StorageCopyOperationResult(true, toIdentifier);
        }

        public override Task<string[]> List(string identifier, StorageListOptions options)
        {
            List<string> items = new List<string>();
            TaskCompletionSource<string[]> taskCompletionSource = new TaskCompletionSource<string[]>();
            GetUserDataRequest request = new GetUserDataRequest();
            PlayFabClientAPI.GetUserData(request, result =>
            {
                foreach (var obj in result.Data)
                {
                    items.Add(obj.Key);
                }
                taskCompletionSource.SetResult(items.FindAll(item => item.Contains(identifier)).ToArray());
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(items.ToArray());
            });
            return taskCompletionSource.Task;
        }

        public override Task<string[]> ListAll()
        {
            return List(string.Empty, new StorageListOptions()
            {
                Recurse = true
            });
        }

    }

    /// <summary>
    /// The PlayFab user data storage stream wrapper.
    /// </summary>
    public class PlayFabUserDataStorageStream : StorageStream
    {

        protected readonly MemoryStream memoryStream;

        public virtual MemoryStream MemoryStream
        {
            get
            {
                return this.memoryStream;
            }
        }

        public PlayFabUserDataStorageStream(string identifier, MemoryStream memoryStream) : base(identifier, memoryStream)
        {
            this.memoryStream = memoryStream;
        }

    }

}