#include "config_tab_worker.h"
#include "projects_utils.h"
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <library/cpp/yconf/patcher/unstrict_config.h>
#include <library/cpp/logger/global/global.h>
#include <util/folder/path.h>
#include <library/cpp/json/json_reader.h>

namespace NRTYDeploy {
    using namespace NDMInterface;
    class TConfigTabWorkerDiff : public TConfigTabWorker {
    public:

        void SetChangedConfigImpl(const TConfigTab& tab, IVersionedStorage& storage) override {
            TString data;
            if (tab.GetFilePath().find("diff") != TString::npos) {
                NJson::TJsonValue diff(NJson::JSON_MAP);
                for (const auto& field : tab.GetFields())
                    if (field.GetDefaultState() != TConfigField::IS_DEFAULT)
                        diff.InsertValue(field.GetName(), field.GetValue());
                data = diff.GetStringRobust();
            } else {
                TUnstrictConfig cfg;
                VERIFY_WITH_LOG(cfg.ParseMemory(""), "cannot parse empty config");
                for (const auto& field : tab.GetFields())
                    cfg.PatchEntry(field.GetName(), field.GetValue());
                data = cfg.ToString();
            }
            if (!storage.SetValue(tab.GetFilePath(), data))
                ythrow yexception() << "cannot write " << tab.GetFilePath();
        }

    protected:
        class TConfigFields : public THashMap <TString, TConfigField> {
        public:
            TConfigFields() {
                Default.SetType(TConfigField::STRING);
            }

            const TConfigField& Get(const TString& name) const {
                const_iterator i = find(name);
                if (i.IsEnd())
                    return Default;
                return i->second;
            }

        protected:
            void RegisterInt(const TString& name, i64 defaultValue, i64 min = Min<i64>(), i64 max = Max<i64>()) {
                value_type::second_type& field = InitField(name, TConfigField::INTEGER, ToString(defaultValue));
                field.SetMinValue(min);
                field.SetMaxValue(max);
            }

            void RegisterFloat(const TString& name, double defaultValue, double min = -Max<double>(), double max = Max<double>()) {
                value_type::second_type& field = InitField(name, TConfigField::FLOAT, ToString(defaultValue));
                field.SetMinValue(min);
                field.SetMaxValue(max);
            }

            void RegisterBool(const TString& name, bool defaultValue) {
                InitField(name, TConfigField::BOOLEAN, ToString(defaultValue));
            }

            void RegisterEnum(const TString& name, const TString defaultValue, ...) {
                value_type::second_type& field = InitField(name, TConfigField::ENUM, defaultValue);
                va_list arglist;
                va_start(arglist, defaultValue);
                TStringBuf val;
                while (!(val = va_arg(arglist, const char*)).empty())
                    *field.AddAllowedValues() = val;
                va_end(arglist);
            }

        private:
            value_type::second_type& InitField(const TString& name, TConfigField::TType type, const TString& defaultValue) {
                value_type::second_type& field = operator[](name);
                SetFieldName(field, name);
                field.SetType(type);
                SetFieldValue(field, defaultValue);
                field.SetDefaultValue(field.GetValue());
                field.SetDefaultState(TConfigField::IS_DEFAULT);
                return field;
            }
            TConfigField Default;
        };

        class TDiffConfigTabFiller {
        public:
            TDiffConfigTabFiller(TConfigTab& tab, IVersionedStorage& storage, const TConfigFields& configFields,
                const TProjectConfig::TFiles::TFile* defaultConfig, const TProjectConfig::TFiles::TFile* configDiff,
                const char* rootSectionPath)
                : Tab(tab)
                , Storage(storage)
                , ConfigFields(configFields)
                , DefaultConfig(defaultConfig)
                , ConfigDiff(configDiff) {
                RootSectionPath = SplitString(rootSectionPath, ".");
            }
            void Process() const {
                for (const auto& f : ConfigFields) {
                    TConfigField& field = *Tab.AddFields();
                    AddedFields[f.first] = &field;
                    field = f.second;
                }
                if (!!DefaultConfig) {
                    ReadYConfig(DefaultConfig, true);
                    ReadPatch();
                } else {
                    if (!ConfigDiff)
                        Tab.SetEnabled(false);
                    else
                        ReadYConfig(ConfigDiff, false);
                }
            }
        private:
            void ReadYConfig(const TProjectConfig::TFiles::TFile* file, bool isDefault) const {
                TString data;
                if (!Storage.GetValue(file->GetPath(), data, file->GetVersion()))
                    ythrow yexception() << "cannot get data from " << file->GetPath() << ", version " << file->GetVersion();
                TUnstrictConfig cfg;
                if (!cfg.ParseMemory(data.data())) {
                    TString err;
                    cfg.PrintErrors(err);
                    ythrow yexception() << "corrupted file " << file->GetPath() << ", version " << file->GetVersion() << ":" << err;
                }
                TYandexConfig::Section* s = cfg.GetRootSection();
                if (!s)
                    return;
                for (const auto& name : RootSectionPath) {
                    TYandexConfig::TSectionsMap sections = s->GetAllChildren();
                    TYandexConfig::TSectionsMap::iterator i = sections.find(name);
                    if (i == sections.end())
                        return;
                    s = i->second;
                }
                ReadYConfigSection(s, isDefault, "");
            }

            void ReadYConfigSection(TYandexConfig::Section* s, bool isDefault, TString prefix) const {
                for (const auto& dir : s->GetDirectives()) {
                    TString name = prefix + dir.first;
                    TConfigField*& field = AddedFields[name];
                    if (!field)
                        field = Tab.AddFields();
                    *field = ConfigFields.Get(name);
                    SetFieldName(*field, name);
                    field->SetDefaultState(TConfigField::NO_DEFAULT);
                    SetFieldValue(*field, dir.second);
                    if (isDefault) {
                        field->SetDefaultState(TConfigField::IS_DEFAULT);
                        field->SetDefaultValue(dir.second);
                    }
                }

                TYandexConfig::TSectionsMap sections = s->GetAllChildren();
                for (auto& child : sections)
                    ReadYConfigSection(child.second, isDefault, prefix + child.first + ".");
            }

            void ReadPatch() const {
                if (!ConfigDiff)
                    return;
                TString data;
                if (!Storage.GetValue(ConfigDiff->GetPath(), data, ConfigDiff->GetVersion()))
                    ythrow yexception() << "cannot get data from " << ConfigDiff->GetPath() << ", version " << ConfigDiff->GetVersion();
                TStringInput si(data);
                NJson::TJsonValue json;
                if (!NJson::ReadJsonTree(&si, &json))
                    return;
                for (const auto& field : json.GetMap()) {
                    TConfigField*& f = AddedFields[field.first];
                    if (!f) {
                        f = Tab.AddFields();
                        *f = ConfigFields.Get(field.first);
                        SetFieldName(*f, field.first);
                        f->SetDefaultValue("");
                    }
                    f->SetDefaultState(TConfigField::HAS_DEFAULT);
                    SetFieldValue(*f, field.second.GetStringRobust());
                }

            }

            TConfigTab& Tab;
            IVersionedStorage& Storage;
            const TConfigFields& ConfigFields;
            const TProjectConfig::TFiles::TFile* DefaultConfig;
            const TProjectConfig::TFiles::TFile* ConfigDiff;
            TVector<TString> RootSectionPath;
            mutable THashMap<TString, TConfigField*> AddedFields;
        };
    };

    class TConfigTabWorkerRTYMain : public TConfigTabWorkerDiff {
    public:
        void FillTabImpl(TConfigTab& tab, IVersionedStorage& storage, const TProjectConfig::TComponent* component, const TProjectConfig& projectConfig) const override {
            tab.SetRequired(true);
            tab.SetType(TConfigTab::RTYSERVER_MAIN);
            tab.SetTabName("Common Search");
            tab.SetEnabled(true);
            tab.SetFilePath(GetConfigDir(component->GetComponent()) + "/rtyserver.diff-" + component->GetComponent().GetServiceName());

            const TProjectConfig::TFiles::TFile* defaultConfig(nullptr);
            const TProjectConfig::TFiles::TFile* configDiff(nullptr);

            for (const auto& file : component->GetFiles().GetFile()) {
                if (TFsPath(file.GetPath()).GetName().StartsWith("rtyserver.diff")) {
                    configDiff = &file;
                    tab.SetFilePath(file.GetPath());
                    break;
                }
            }
            for (const auto& file : projectConfig.GetCommonFiles().GetFile()) {
                if (TFsPath(file.GetPath()).GetName().StartsWith("rtyserver.conf")) {
                    defaultConfig = &file;
                    break;
                }
            }
            TDiffConfigTabFiller(tab, storage, Default<TRTYServerConfigFields>(), defaultConfig, configDiff, "Server").Process();
        }

        static TFactory::TRegistrator<TConfigTabWorkerRTYMain> Registrator;

    protected:
        class TRTYServerConfigFields : public TConfigFields {
        public:
            TRTYServerConfigFields() {
                RegisterInt("ShardsNumber", 1, 1, 50);
                RegisterBool("IsPrefixedIndex", false);
                RegisterEnum("Searcher.ArchivePolicy", "MAPMEM", "MAPMEMLOCK", "INMEM", "INMEMLOCK", "DISK", "");
            }
        };
    };

    TConfigTabWorkerRTYMain::TFactory::TRegistrator<TConfigTabWorkerRTYMain> TConfigTabWorkerRTYMain::Registrator(TConfigTab::RTYSERVER_MAIN);
}
