#include "rty_diff.h"
#include <library/cpp/logger/global/global.h>
#include <library/cpp/yconf/patcher/config_patcher.h>
#include <saas/deploy_manager/meta/cluster.h>
#include <library/cpp/json/json_reader.h>

#define RTY_DIFF_FILE_CONTENT_GENERATOR_TYPE "rty_diff"

namespace NRTYDeploy {

    TRtyDiffFileContentGenerator::TRtyDiffFileContentGenerator()
        : Rename("rtyserver.conf-common")
        , Prefix("Server.")
        , ClusterMetaVersion(-1)
    {}

    TRtyDiffFileContentGenerator::TRtyDiffFileContentGenerator(const TFsPath& path, const TString& def, const TString& rename, const TString& prefix, const ICommonData& common, const TString& ctype, const TString& service, i64 version)
        : Rename(rename)
        , Prefix(prefix)
        , ClusterMetaVersion(-1)
    {
        TVector<TPtr> listQL;
        TVector<TPtr> listCTDiff;
        Diff.Reset(new TPlainFileContentGenerator(path, common, version));
        NSaas::TSlotInfo slotInfo;
        slotInfo.CType = ctype;
        slotInfo.Service = service;
        AddGenerators(listQL, common, path.Parent(), "query-language", slotInfo);
        AddGenerators(listCTDiff, common, path.Parent(), "rtyserver.ctdiff-" + ctype + "-", slotInfo);
        if (!listQL.empty()) {
            if (listQL.size() > 1)
                ythrow yexception() << "there are " << listQL.size() << " query-languages";
            QueryLanguage = listQL.front();
        }
        if (!listCTDiff.empty()) {
            if (listCTDiff.size() > 1)
                ythrow yexception() << "there are " << listCTDiff.size() << " ctype-diffs";
            CTypeDiff = listCTDiff.front();
        }
        TVector<TPtr> listDC;
        AddGenerators(listDC, common, "/defaults/", def, slotInfo);
        if (listDC.size() > 1)
            ythrow yexception() << "there are " << listDC.size() << " defaults configs";
        DefaultConfig = listDC.front();
        NSaas::TClusterLocker::GetVersion(common, ctype, ClusterMetaVersion);
    }

    TString TRtyDiffFileContentGenerator::GetType() const {
        return RTY_DIFF_FILE_CONTENT_GENERATOR_TYPE;
    }

    TString TRtyDiffFileContentGenerator::GetRename(const TContext& /*context*/) const {
        return Rename;
    }

    void TRtyDiffFileContentGenerator::Serialize(NJson::TJsonValue& json) const {
        TFileContentGenerator::Serialize(json);
        json.InsertValue("rename", Rename);
        json.InsertValue("prefix", Prefix);
        Diff->Serialize(json.InsertValue("diff", NJson::JSON_MAP));
        if (!!QueryLanguage)
            QueryLanguage->Serialize(json.InsertValue("query_language", NJson::JSON_MAP));
        if (!!CTypeDiff)
            CTypeDiff->Serialize(json.InsertValue("ctype_diff", NJson::JSON_MAP));
        if (!!DefaultConfig)
            DefaultConfig->Serialize(json.InsertValue("default_config", NJson::JSON_MAP));
        if (ClusterMetaVersion >= 0)
            json.InsertValue("cluster_meta_version", ClusterMetaVersion);
    }

    void TRtyDiffFileContentGenerator::DoDeserialize(const NJson::TJsonValue& json) {
        TFileContentGenerator::DoDeserialize(json);
        if (json.Has("rename"))
            Rename = json["rename"].GetStringRobust();
        if (json.Has("prefix"))
            Prefix = json["prefix"].GetStringRobust();
        if (json.Has("diff"))
            Diff.Reset(Deserialize(json["diff"]));
        else {
            Diff.Reset(new TPlainFileContentGenerator);
            Diff->DoDeserialize(json);
        }
        if (json.Has("query_language"))
            QueryLanguage.Reset(Deserialize(json["query_language"]));
        if (json.Has("ctype_diff"))
            CTypeDiff.Reset(Deserialize(json["ctype_diff"]));
        if (json.Has("default_config"))
            DefaultConfig.Reset(Deserialize(json["default_config"]));
        if (json.Has("cluster_meta_version"))
            ClusterMetaVersion = json["cluster_meta_version"].GetIntegerSafe();
        else
            ClusterMetaVersion = -1;
    }

    bool TRtyDiffFileContentGenerator::GetContent(TString& result, const ICommonData& common, const TContext& context) const {
        if (!Diff->GetContent(result, common, context))
            return false;
        if (!context.NoAnyPatch) {
            ApplyToDefault(result, common, context);
            ApplyCTypeDiff(result, common, context);
            PatchDocFetcher(result, common, context);
            PatchQueryLanguage(result, common, context);
        }
        return true;
    }

    TString TRtyDiffFileContentGenerator::Patch(const TString& original, const TString& patch) const {
        NJson::TJsonValue parsedPatch;
        TStringInput ss(patch);
        if (!NJson::ReadJsonTree(&ss, true, &parsedPatch, true)) {
            ythrow yexception() << "Cannot parse patch as json";
        }
        const NJson::TJsonValue::TMapType* values;
        if (!parsedPatch.GetMapPointer(&values)) {
            ythrow yexception() << "unable to get map pointer in the json patch.";
        }
        NJson::TJsonValue processedPatch(NJson::JSON_MAP);
        for (const auto& value : *values) {
            if (!value.second.IsMap()) {
                processedPatch[Prefix + value.first] = value.second;
            } else {
                const TString customPrefix = value.first + ".";
                for (const auto& subValue: value.second.GetMap()) {
                    processedPatch[customPrefix + subValue.first] = subValue.second;
                }
            }
        }
        NConfigPatcher::TOptions options;
        return NConfigPatcher::Patch(original, processedPatch, options);
    }

    void TRtyDiffFileContentGenerator::ApplyToDefault(TString& result, const ICommonData& common, const TContext& context) const {
        TString def;
        if (!DefaultConfig || !DefaultConfig->GetContent(def, common, context))
            ythrow yexception() << "cannot get default rtyserver.conf";
        result = Patch(def, result);
    }

    void TRtyDiffFileContentGenerator::ApplyCTypeDiff(TString& result, const ICommonData& common, const TContext& context) const {
        TString ctDiff;
        if (!CTypeDiff || !CTypeDiff->GetContent(ctDiff, common, context))
            return;
        result = Patch(result, ctDiff);
    }

    void TRtyDiffFileContentGenerator::PatchDocFetcher(TString& result, const ICommonData& common, const TContext& context) const {
        if (!!context.SlotInfo.CType) {
            if (ClusterMetaVersion < 0)
                NSaas::TClusterLocker::GetVersion(common, context.SlotInfo.CType, ClusterMetaVersion);

            const NSaas::TClusterConst* cluster = NSaas::TClusterLocker::DefineConst(common, context.SlotInfo.CType, ClusterMetaVersion);
            if (!cluster) {
                ythrow yexception()
                    << "Cannot get cluster info for ctype=" << context.SlotInfo.CType
                    << " service=" << context.SlotInfo.Service;
            }

            const NSearchMapParser::TSearchMapService* service = cluster->ServiceSearchMap(context.SlotInfo.Service);

            if (!service) {
                if (context.SlotInfo.Service != "unused") {
                    ERROR_LOG << "Cannot find service=" << context.SlotInfo.Service
                    << " in cluster info for ctype=" << context.SlotInfo.CType;
                }
                return;
            }
            const TString& distributors = service->GetDistributors();
            if (!!distributors) {
                NJson::TJsonValue jsonPatch;
                jsonPatch.InsertValue("ModulesConfig.DOCFETCHER.Stream.DistributorServers", distributors);
                jsonPatch.InsertValue("ModulesConfig.DOCFETCHER.Stream.DistributorStream", service->Stream);
                NConfigPatcher::TOptions options;
                options.Prefix = Prefix;
                result = NConfigPatcher::Patch(result, jsonPatch, options);
            }
        }
    }

    void TRtyDiffFileContentGenerator::PatchQueryLanguage(TString& result, const ICommonData& /*common*/, const TContext& context) const {
        if (!QueryLanguage)
            return;
        TString ql = QueryLanguage->GetRename(context);
        if (!ql)
            return;
        size_t qpos = result.find("query-language");
        if (qpos != TString::npos)
            result.replace(qpos, TString("query-language").length(), ql);
    }

    void TRtyDiffFileContentGenerator::SetLastVersion() {
        TFileContentGenerator::SetLastVersion();
        Diff->SetLastVersion();
        if (!!QueryLanguage)
            QueryLanguage->SetLastVersion();
        if (!!CTypeDiff)
            CTypeDiff->SetLastVersion();
        if (!!DefaultConfig)
            DefaultConfig->SetLastVersion();
        ClusterMetaVersion = -1;
    }

    TRtyDiffFileContentGenerator::TFactory::TRegistrator<TRtyDiffFileContentGenerator> TRtyDiffFileContentGenerator::Registrar(RTY_DIFF_FILE_CONTENT_GENERATOR_TYPE);

}
