#pragma once

#include <balancer/kernel/http/parser/http.h>
#include <util/generic/string.h>
#include <util/generic/vector.h>
#include <balancer/kernel/memory/chunks.h>
#include <library/cpp/digest/murmur/murmur.h>
#include <util/thread/lfqueue.h>
#include <util/generic/queue.h>
#include "storage_key.h"

namespace NSrvKernel::NStorage {

    struct TStorageItem {
        TString Body;
        TResponse Response;
        bool Contains = false;
        TInstant InsertTime;

        TStorageItem() = default;

        TStorageItem (TString body, TResponse response, bool contains, TInstant time) {
            Body = body;
            Response = std::move(response);
            Contains = contains;
            InsertTime = time;
        }
    };

    struct TDisposableKey {
        TStorageKey Key;
        TInstant Time;
    };

    class TGCQueue {
        public:

        void Enqueue(TDisposableKey key) {
            TGuard<TMutex> guard(Mutex_);
            GCQueue_.push(key);
        }

        void Dequeue() {
            TGuard<TMutex> guard(Mutex_);
            return GCQueue_.pop();
        }

        bool IsEmpty() {
            TGuard<TMutex> guard(Mutex_);
            return GCQueue_.size() == 0;
        }

        TDisposableKey Peek() {
            TGuard<TMutex> guard(Mutex_);
            return GCQueue_.front();
        }

        TMutex Mutex_;
        TQueue<TDisposableKey> GCQueue_;
    };

    class TStorage {
    private:
        TMutex& GetMutex(const TStorageKey&) {
            return Mutex_;
            //return MutexVector_.at(GetIndex(key));
        }

        THashMap<TStorageKey, TStorageItem>& GetHashMap (const TStorageKey& ) {
            return HashMap_;
            //return HashMapVector_[GetIndex(key)];
        }

        //size_t GetIndex(const TStorageKey& key) {
        //    return (static_cast<size_t>(key)) % MutexNumber_;
        //}

    public:
        TStorage(int) {}
            // MutexNumber_(n) {
            //for (int i = 0; i < n; i ++) {
            //    MutexVector_.push_back(TMutex());
            //    HashMapVector_.push_back(THashMap<TStorageKey, TStorageItem>());
            //}
        //}

        //void InitializeShards(int) {
            //MutexNumber_ = n;
            //MutexVector_ = TVector<TMutex>();
            //HashMapVector_ = TVector<THashMap<TStorageKey, TStorageItem>>();
            //
            //for (int i = 0; i < n; i ++) {
            //    MutexVector_.push_back(TMutex());
            //    HashMapVector_.push_back(THashMap<TStorageKey, TStorageItem>());
            //}
        //}

        bool Contains(const TStorageKey& key) {
            return GetHashMap(key).contains(key);
        }

        TStorageItem Get(const TStorageKey& key) {
            TGuard<TMutex> mutexGuard (GetMutex(key));
            auto& map = GetHashMap(key);

            if (map.contains(key)) {
                return map[key];
            }

            return TStorageItem();
        }

        void StoreData(TStorageKey key, TResponse response, TString body) {
            {
                TGuard<TMutex> guard(GetMutex(key));
                auto& map = GetHashMap(key);

                if (map.contains(key)) {
                    return;
                }
                //auto it = map.at(key);
                //*it = {body, std::move(response), true};
                //map[key].BodyList = body;//std::move(chunkList);
                //map[key].Response = std::move(response);
                //map[key].Contains = true;
                map[key] = TStorageItem (body, std::move(response), true, TInstant::Now());
            }

            GCQueue_.Enqueue({key, Now() + CacheTTL_});
        }

        TMaybe<TInstant> Cleanup () {
            while (!GCQueue_.IsEmpty() && GCQueue_.Peek().Time < TInstant::Now()) {
                const auto key = GCQueue_.Peek().Key;

                {
                    TGuard<TMutex>(GetMutex(key));
                    auto& map = GetHashMap(key);
                    GCQueue_.Dequeue();
                    map.erase(map.find(key));
                }
            }

            if (GCQueue_.IsEmpty()) {
                return TInstant::Now() + CacheTTL_;
            }

            return GCQueue_.Peek().Time;
        }

        TDuration CacheTTL_ = TDuration::Seconds(5);

        TMutex Mutex_;
        THashMap<TStorageKey, TStorageItem> HashMap_;
        TGCQueue GCQueue_;
        //TVector<THashMap<TStorageKey, TStorageItem>> HashMapVector_;
        //TVector<TMutex> MutexVector_;
        //size_t MutexNumber_;

    };
}
