#pragma once

#include <saas/deploy_manager/storage/abstract.h>
#include <saas/util/logging/trace.h>
#include <util/system/mutex.h>
#include <util/folder/path.h>

namespace NRTYDeploy {
    class TProfileStorage : public IVersionedStorage {
    public:
        TProfileStorage(TRequestContext& context, IVersionedStorage& storage)
            : IVersionedStorage(storage.GetOptions())
            , Context(context)
            , Storage(storage)
        {}

        virtual bool RemoveNode(const TString& key, bool withHistory = false) const override {
            NUtil::TTracer timer(GetRCText() + "RemoveNode " + key);
            TGuard<TMutex> g(Mutex);
            TString path = TFsPath("/" + key).Fix().GetPath();
            ValueCache.erase(path);
            return Storage.RemoveNode(key, withHistory);
        }

        virtual bool ExistsNode(const TString& key) const override {
            NUtil::TTracer timer(GetRCText() + "ExistsNode " + key);
            TGuard<TMutex> g(Mutex);
            TString path = TFsPath("/" + key).Fix().GetPath();
            if (ValueCache.contains(path))
                return true;
            return Storage.ExistsNode(key);
        }

        virtual bool GetVersion(const TString& key, i64& version) const override {
            NUtil::TTracer timer(GetRCText() + "GetVersion " + key);
            return Storage.GetVersion(key, version);
        }

        virtual bool GetNodes(const TString& key, TVector<TString>& result, bool withDirs = false) const override {
            NUtil::TTracer timer(GetRCText() + "GetNodes " + key);
            return Storage.GetNodes(key, result, withDirs);
        }

        virtual bool GetValue(const TString& key, TString& result, i64 version = -1, bool lock = true) const override {
            NUtil::TTracer timer(GetRCText() + "GetValue " + key + " " + ToString(version));
            if (version != -1)
                return Storage.GetValue(key, result, version, lock);
            TGuard<TMutex> g(Mutex);
            TString path = TFsPath("/" + key).Fix().GetPath();
            TValueCache::iterator iter = ValueCache.find(path);
            if (iter != ValueCache.end()) {
                DEBUG_LOG << GetRCText() << "Load value from cache " + key << Endl;
                result = iter->second;
                return true;
            }
            if (!Storage.GetValue(path, result, version, lock))
                return false;
            ValueCache[path] = result;
            return true;
        }

        virtual bool SetValue(const TString& key, const TString& value, bool storeHistory = true, bool lock = true, i64* version = nullptr) const override {
            NUtil::TTracer timer(GetRCText() + "SetValue " + key);
            TGuard<TMutex> g(Mutex);
            if (!Storage.SetValue(key, value, storeHistory, lock, version))
                return false;
            TString path = TFsPath("/" + key).Fix().GetPath();
            ValueCache.erase(path);
            return true;
        }

        virtual TAbstractLock::TPtr WriteLockNode(const TString& path, TDuration timeout = TDuration::Seconds(100000)) const override {
            NUtil::TTracer timer(GetRCText() + "WriteLockNode " + path);
            return Storage.WriteLockNode(path, timeout);
        }

        virtual TAbstractLock::TPtr ReadLockNode(const TString& path, TDuration timeout = TDuration::Seconds(100000)) const override {
            NUtil::TTracer timer(GetRCText() + "ReadLockNode " + path);
            return Storage.ReadLockNode(path, timeout);
        }

        virtual bool CreatePersistentSequentialNode(const TString& key, const TString& data) const override {
            try {
                NUtil::TTracer timer(GetRCText() + "CreatePersistentSequentialNode " + key);
                Storage.CreatePersistentSequentialNode(key, data);
                return true;
            } catch (...) {
                return false;
            }
        }

    protected:
        virtual TAbstractLock::TPtr NativeWriteLockNode(const TString& /*path*/, TDuration /*timeout*/ = TDuration::Seconds(100000)) const override {
            FAIL_LOG("The method or operation is not implemented.");
        }

        virtual TAbstractLock::TPtr NativeReadLockNode(const TString& /*path*/, TDuration /*timeout*/ = TDuration::Seconds(100000)) const override {
            FAIL_LOG("The method or operation is not implemented.");
        }

    private:
        inline TString GetRCText() const {
            return "RequestId=" + ToString(Context.Id) + ": ";
        }
        TRequestContext& Context;
        IVersionedStorage& Storage;
        typedef THashMap<TString, TString> TValueCache;
        TMutex Mutex;
        mutable TValueCache ValueCache;
    };
}
