#include "get_sla_table.h"

#include <saas/deploy_manager/modules/cache/cache_module.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/scripts/common/sla_processing/sla_processing.h>

#include <saas/library/searchmap/searchmap.h>

#include <util/string/type.h>

using namespace NRTYDeploy;

namespace {
    struct TCacheCreator: public TCacheModule::ICacheCreator {
        class TCache: public TCacheModule::ICache {
        private:
            ui64 Timestamp = 0;
            TRWMutex RWLock;
            TVector<std::pair<TString, TString>> Entries;

        public:
            void UpsertEntry(const TString& cacheKey, const ui64 timestamp, const TString& value) {
                if (timestamp < Timestamp)
                    return;
                TWriteGuard wg(RWLock);
                if (timestamp > Timestamp) {
                    Entries.clear();
                    Timestamp = timestamp;
                } else {
                    for (auto& entry: Entries) {
                        if (entry.first == cacheKey) {
                            entry.second = value;
                            return;
                        }
                    }
                }
                Entries.emplace_back(cacheKey, value);
            }

            TMaybe<TString> GetEntry(const TString& cacheKey, const ui64 timestamp) const {
                DEBUG_LOG << "GetEntry(): " << timestamp << " " << Timestamp << Endl;
                TReadGuard rg(RWLock);
                if (timestamp == Timestamp) {
                    for (const auto& entry: Entries) {
                        if (entry.first == cacheKey) {
                            return entry.second;
                        }
                    }
                }
                return Nothing();
            }
        };

        THolder<TCacheModule::ICache> CreateCache() const override {
            return THolder(new TCacheCreator::TCache());
        }
    };

    const TCacheCreator CREATOR;

    TCacheCreator::TCache* GetCache(IDeployInfoRequest& request) {
        return (TCacheCreator::TCache*)request.GetCacheModule().GetCache("get_sla_table", CREATOR);
    }

    inline TString CacheKey(const TString& ctype, const TString& component) {
        return ctype + "-" + component;
    }
}

bool TScriptGetSLATable::Process(IDeployInfoRequest& request) {
    const TString& ctype = request.GetRD().CgiParam.Get("ctype");
    const bool skipCache = IsTrue(request.GetRD().CgiParam.Get("nocache"));
    const TString component = request.GetRD().CgiParam.Has("component") ? request.GetRD().CgiParam.Get("component") : "rtyserver";
    const TString cacheKey = CacheKey(ctype, component);
    ui64 timestamp = 0;
    ui64 now = 0;
    bool expired = false;
    TCacheCreator::TCache* cache = GetCache(request);
    if (!skipCache && request.GetStorage().ExistsNode(GetSLATimestampPath())) {
        TString timestampStr;
        if (request.GetStorage().GetValue(GetSLATimestampPath(), timestampStr) &&
            TryFromString(timestampStr, timestamp))
        {
            now = Now().MilliSeconds();
            expired = (now - timestamp > ui64(24) * 3600 * 1000);
            if (!expired) {
                const auto entry = cache->GetEntry(cacheKey, timestamp);
                if (entry.Defined()) {
                    request.Output() << "HTTP/1.1 200 \r\n\r\n";
                    request.Output() << entry.GetRef();
                    return true;
                }
            }
        }
    }

    NJson::TJsonValue json(NJson::JSON_MAP);

    NRTYDeployInfo::IDeployComponentInfo::TPtr info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(component);
    info->SetInfo(&request, ctype);
    const NSearchMapParser::TSearchMap* ism = &info->SearchMap();

    try {
        for (const auto& service : ism->GetInternalSearchMap()) {
            NSaasProto::TSlaDescription description;
            if (!TryGetSLADescription(service.Name, request.GetStorage(), description)) {
                ythrow yexception() << "Failed to parse protobuf, service=" << service.Name;
            }
            json.InsertValue(service.Name, SerializeDescription(description, ctype));
        }
    } catch (yexception& e) {
        request.Output() << "HTTP/1.1 500 \r\n\r\n";
        request.Output() << "Action failed: " << CurrentExceptionMessage();
        return false;
    }
    TStringStream ss;
    NJson::WriteJson(&ss, &json, true, true, false);

    if (!skipCache) {
        if (expired) {
            cache->UpsertEntry(cacheKey, now, ss.Str());
            request.GetStorage().SetValue(GetSLATimestampPath(), ToString(now), false);
        } else
            cache->UpsertEntry(cacheKey, timestamp, ss.Str());
    }

    request.Output() << "HTTP/1.1 200 \r\n\r\n";
    request.Output() << ss.Str();
    return true;
}

static NRTYDeploy::TScriptGetSLATable::TFactory::TRegistrator<TScriptGetSLATable> Registrator("get_sla_table");
