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

using PlayFab;
using PlayFab.DataModels;
using PlayFab.Internal;

using UnityEngine;

namespace Bayat.SaveSystem.Storage
{

    public class PlayFabEntityFilesStorage : StorageBase
    {

        public const string DefaultEntityType = "title_player_account";

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

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

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

        protected virtual Task<GetFileMetadata> GetFile(string fileName)
        {
            var tcs = new TaskCompletionSource<GetFileMetadata>();
            var request = new GetFilesRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            PlayFabDataAPI.GetFiles(request, result =>
            {
                GetFileMetadata fileMetadata = null;
                foreach (var filePair in result.Metadata)
                {
                    if (filePair.Value.FileName == fileName)
                    {
                        fileMetadata = filePair.Value;
                        break;
                    }
                }
                if (fileMetadata == null)
                {
                    tcs.SetException(new FileNotFoundException($"The requested file does not exists {fileName}"));
                    return;
                }
                tcs.SetResult(fileMetadata);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                tcs.SetCanceled();
            });
            return tcs.Task;
        }

        protected override Task CommitWriteStreamInternal(IStorageStream stream)
        {
            var playFabStream = (PlayFabEntityFilesStorageStream)stream;
            var request = new InitiateFileUploadsRequest();
            request.Entity = new PlayFab.DataModels.EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.FileNames = new List<string>() { playFabStream.Identifier };
            var taskCompletionSource = new TaskCompletionSource<byte[]>();
            PlayFabDataAPI.InitiateFileUploads(request, result =>
            {
                PlayFabHttp.SimplePutCall(result.UploadDetails[0].UploadUrl, playFabStream.MemoryStream.ToArray(), payload =>
                {
                    var finalizeRequest = new FinalizeFileUploadsRequest();
                    finalizeRequest.Entity = request.Entity;
                    finalizeRequest.FileNames = request.FileNames;
                    PlayFabDataAPI.FinalizeFileUploads(finalizeRequest, finalizeResult =>
                    {
                        taskCompletionSource.SetResult(payload);
                    }, finalizeError =>
                    {
                        Debug.LogError(finalizeError.ErrorMessage);
                        taskCompletionSource.SetCanceled();
                    });
                }, error =>
                {
                    Debug.LogError(error);
                    taskCompletionSource.SetCanceled();
                });
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override async Task<IStorageStream> GetReadStream(string identifier)
        {
            var tcs = new TaskCompletionSource<IStorageStream>();
            var request = new GetFilesRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            GetFileMetadata fileMetadata;
            try
            {
                fileMetadata = await GetFile(identifier);
            }
            catch
            {
                throw;
            }
            PlayFabHttp.SimpleGetCall(fileMetadata.DownloadUrl, payload =>
            {
                tcs.SetResult(new PlayFabEntityFilesStorageStream(identifier, new MemoryStream(payload)));
            }, error =>
            {
                Debug.LogError(error);
                tcs.SetCanceled();
            });
            return await tcs.Task;
        }

        protected override Task WriteAllTextInternal(string identifier, string data)
        {
            var request = new InitiateFileUploadsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.FileNames = new List<string>() { identifier };
            var tcs = new TaskCompletionSource<byte[]>();
            PlayFabDataAPI.InitiateFileUploads(request, result =>
            {
                PlayFabHttp.SimplePutCall(result.UploadDetails[0].UploadUrl, TextEncoding.GetBytes(data), payload =>
                {
                    var finalizeRequest = new FinalizeFileUploadsRequest();
                    finalizeRequest.Entity = request.Entity;
                    finalizeRequest.FileNames = request.FileNames;
                    PlayFabDataAPI.FinalizeFileUploads(finalizeRequest, finalizeResult =>
                    {
                        tcs.SetResult(payload);
                    }, finalizeError =>
                    {
                        Debug.LogError(finalizeError.ErrorMessage);
                        tcs.SetCanceled();
                    });
                }, error =>
                {
                    Debug.LogError(error);
                    tcs.SetCanceled();
                });
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                tcs.SetCanceled();
            });
            return tcs.Task;
        }

        public override async Task<string> ReadAllText(string identifier)
        {
            var tcs = new TaskCompletionSource<string>();

            GetFileMetadata fileMetadata;
            try
            {
                fileMetadata = await GetFile(identifier);
            }
            catch
            {
                throw;
            }
            PlayFabHttp.SimpleGetCall(fileMetadata.DownloadUrl, payload =>
            {
                tcs.SetResult(TextEncoding.GetString(payload));
            }, error =>
            {
                Debug.LogError(error);
                tcs.SetCanceled();
            });

            return await tcs.Task;
        }

        protected override Task WriteAllBytesInternal(string identifier, byte[] data)
        {
            var request = new InitiateFileUploadsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.FileNames = new List<string>() { identifier };
            var tcs = new TaskCompletionSource<byte[]>();
            PlayFabDataAPI.InitiateFileUploads(request, result =>
            {
                PlayFabHttp.SimplePutCall(result.UploadDetails[0].UploadUrl, data, payload =>
                {
                    var finalizeRequest = new FinalizeFileUploadsRequest();
                    finalizeRequest.Entity = request.Entity;
                    finalizeRequest.FileNames = request.FileNames;
                    PlayFabDataAPI.FinalizeFileUploads(finalizeRequest, finalizeResult =>
                    {
                        tcs.SetResult(payload);
                    }, finalizeError =>
                    {
                        Debug.LogError(finalizeError.ErrorMessage);
                        tcs.SetCanceled();
                    });
                }, error =>
                {
                    Debug.LogError(error);
                    tcs.SetCanceled();
                });
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                tcs.SetCanceled();
            });
            return tcs.Task;
        }

        public override async Task<byte[]> ReadAllBytes(string identifier)
        {
            var tcs = new TaskCompletionSource<byte[]>();

            GetFileMetadata fileMetadata;
            try
            {
                fileMetadata = await GetFile(identifier);
            }
            catch
            {
                throw;
            }
            PlayFabHttp.SimpleGetCall(fileMetadata.DownloadUrl, payload =>
            {
                tcs.SetResult(payload);
            }, error =>
            {
                Debug.LogError(error);
                tcs.SetCanceled();
            });

            return await tcs.Task;
        }

        public override async Task<StorageClearOperationResult> Clear()
        {
            var tcs = new TaskCompletionSource<bool>();
            List<string> removedFiles = new List<string>();
            bool allDeleted = true;
            var request = new GetFilesRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            PlayFabDataAPI.GetFiles(request, async result =>
            {
                var deleteRequest = new DeleteFilesRequest();
                deleteRequest.Entity = new EntityKey()
                {
                    Id = this.entityId,
                    Type = this.entityType
                };
                var fileNames = new List<string>();
                if (result.Metadata.Count == 0)
                {
                    tcs.SetResult(true);
                    return;
                }
                foreach (var filePair in result.Metadata)
                {
                    fileNames.Add(filePair.Value.FileName);
                    removedFiles.Add(filePair.Value.FileName);
                }
                deleteRequest.FileNames = fileNames;
                var deleteTcs = new TaskCompletionSource<bool>();
                PlayFabDataAPI.DeleteFiles(deleteRequest, deleteResult =>
                {
                    deleteTcs.SetResult(true);
                }, error =>
                {
                    Debug.LogError(error.ErrorMessage);
                    deleteTcs.SetResult(false);
                    allDeleted = false;
                });
                await deleteTcs.Task;
                tcs.SetResult(deleteTcs.Task.Result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                tcs.SetResult(false);
            });
            await tcs.Task;
            return new StorageClearOperationResult(allDeleted, removedFiles.ToArray());
        }

        protected override async Task<StorageDeleteOperationResult> DeleteInternal(string identifier)
        {
            var request = new DeleteFilesRequest();
            bool succeed = true;
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.FileNames = new List<string>()
            {
                identifier
            };
            var tcs = new TaskCompletionSource<bool>();
            PlayFabDataAPI.DeleteFiles(request, result =>
            {
                tcs.SetResult(true);
                succeed = true;
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                tcs.SetResult(false);
                succeed = false;
            });
            await tcs.Task;
            return new StorageDeleteOperationResult(succeed);
        }

        public override async Task<bool> Exists(string identifier)
        {
            try
            {
                var fileMetadata = await GetFile(identifier);
                return fileMetadata != null;
            }
            catch
            {
                return false;
            }
        }

        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>();
            var tcs = new TaskCompletionSource<string[]>();
            var request = new GetFilesRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            PlayFabDataAPI.GetFiles(request, result =>
            {
                foreach (var filePair in result.Metadata)
                {
                    items.Add(filePair.Value.FileName);
                }
                if (string.IsNullOrEmpty(identifier))
                {
                    tcs.SetResult(items.ToArray());
                }
                else
                {
                    tcs.SetResult(items.FindAll(item => item.Contains(identifier)).ToArray());
                }
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                tcs.SetResult(items.ToArray());
            });
            return tcs.Task;
        }

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

        /// Disable Meta Data and Catalog handling
        public override Task SaveMetaData(string identifier, StorageMetaData metaData)
        {
            return Task.CompletedTask;
        }

        public override Task<StorageMetaData> LoadMetaData(string identifier)
        {
            return Task.FromResult(new StorageMetaData());
        }

        public override Task SaveCatalog(List<string> catalog)
        {
            return Task.CompletedTask;
        }

        public override Task<List<string>> LoadCatalog()
        {
            return Task.FromResult(new List<string>());
        }

    }

    /// <summary>
    /// The PlayFab entity files storage stream wrapper.
    /// </summary>
    public class PlayFabEntityFilesStorageStream : StorageStream
    {

        protected readonly MemoryStream memoryStream;

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

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

    }

}