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

using Firebase;
using Firebase.Firestore;

namespace Bayat.SaveSystem.Storage
{

    public class FirebaseCloudFirestoreStorage : StorageBase
    {

        protected bool useBase64 = true;
        protected FirebaseFirestore databaseInstance;

        public FirebaseCloudFirestoreStorage(string encodingName, bool useBase64, FirebaseFirestore databaseInstance) : base(encodingName)
        {
            this.useBase64 = useBase64;
            this.databaseInstance = databaseInstance ?? FirebaseFirestore.DefaultInstance;
        }

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

        protected override Task CommitWriteStreamInternal(IStorageStream stream)
        {
            FirebaseCloudFirestoreStorageStream firebaseStream = (FirebaseCloudFirestoreStorageStream)stream;
            string data = null;
            if (useBase64)
            {
                data = Convert.ToBase64String(firebaseStream.MemoryStream.ToArray());
            }
            else
            {
                data = this.TextEncoding.GetString(firebaseStream.MemoryStream.ToArray());
            }
            return this.databaseInstance.Document(stream.Identifier).SetAsync(data);
        }

        public override async Task<IStorageStream> GetReadStream(string identifier)
        {
            DocumentSnapshot documentSnapshot = await this.databaseInstance.Document(identifier).GetSnapshotAsync();
            string rawValue = documentSnapshot.ConvertTo<string>();
            MemoryStream memoryStream = null;
            if (useBase64)
            {
                memoryStream = new MemoryStream(Convert.FromBase64String(rawValue));
            }
            else
            {
                memoryStream = new MemoryStream(this.TextEncoding.GetBytes(rawValue));
            }
            return new FirebaseCloudFirestoreStorageStream(identifier, memoryStream);
        }

        protected override Task WriteAllTextInternal(string identifier, string data)
        {
            return this.databaseInstance.Document(identifier).SetAsync(data);
        }

        public override async Task<string> ReadAllText(string identifier)
        {
            DocumentSnapshot documentSnapshot = await this.databaseInstance.Document(identifier).GetSnapshotAsync();
            return documentSnapshot.ConvertTo<string>();
        }

        protected override Task WriteAllBytesInternal(string identifier, byte[] data)
        {
            string stringData = null;
            if (useBase64)
            {
                stringData = Convert.ToBase64String(data);
            }
            else
            {
                stringData = this.TextEncoding.GetString(data);
            }
            return this.databaseInstance.Document(identifier).SetAsync(stringData);
        }

        public override async Task<byte[]> ReadAllBytes(string identifier)
        {
            DocumentSnapshot documentSnapshot = await this.databaseInstance.Document(identifier).GetSnapshotAsync();
            string rawValue = documentSnapshot.ConvertTo<string>();
            byte[] data = null;
            if (useBase64)
            {
                data = Convert.FromBase64String(rawValue);
            }
            else
            {
                data = this.TextEncoding.GetBytes(rawValue);
            }
            return data;
        }

        public override async Task<StorageClearOperationResult> Clear()
        {
            throw new NotImplementedException("The Firebase Cloud Firestore does not allow deleting collections and subcollections from client SDKs, to do so, use the Firebase Console");
        }

        protected override async Task<StorageDeleteOperationResult> DeleteInternal(string identifier)
        {
            await this.databaseInstance.Document(identifier).DeleteAsync();
            return new StorageDeleteOperationResult(true);
        }

        public override async Task<bool> Exists(string identifier)
        {
            DocumentSnapshot documentSnapshot = await this.databaseInstance.Document(identifier).GetSnapshotAsync();
            return documentSnapshot.Exists;
        }

        protected override async Task<StorageMoveOperationResult> MoveInternal(string oldIdentifier, string newIdentifier, bool replace)
        {
            string data = await ReadAllText(oldIdentifier);
            await WriteAllText(newIdentifier, data);
            await Delete(oldIdentifier);
            return new StorageMoveOperationResult(true, 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 async Task<string[]> List(string identifier, StorageListOptions options)
        {
            List<string> items = new List<string>();
            QuerySnapshot query = await this.databaseInstance.Collection(identifier).GetSnapshotAsync();
            foreach (var document in query.Documents)
            {
                items.Add(identifier + document.Id);
                if (options.Recurse)
                {
                    items.AddRange(await List(identifier + document.Id, options));
                }
            }
            return items.ToArray();
        }

        public override Task<string[]> ListAll()
        {
            throw new NotImplementedException("Use List instead, this method is not implemented for Firebase Cloud Firestore.");
        }

    }

    /// <summary>
    /// The Firebase Realtime Database storage stream wrapper.
    /// </summary>
    public class FirebaseCloudFirestoreStorageStream : StorageStream
    {

        protected readonly MemoryStream memoryStream;

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

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

    }

}