#include "projects_utils.h"
#include <google/protobuf/text_format.h>
#include <library/cpp/protobuf/json/proto2json.h>
#include <library/cpp/protobuf/json/json2proto.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>

namespace NRTYDeploy {

    class TCamelToSnakeCase : public NProtobufJson::IStringTransform {
        int GetType() const override {
            return 0;
        }

        void Transform(TString& str) const override {
            if (!str)
                return;
            TString result;
            bool prevWasLower = false;
            for (const char* c = str.begin(); c != str.end(); ++c) {
                bool isUpper = csYandex.IsUpper(*c);
                if (isUpper && prevWasLower)
                    result.append('_');
                result.append(csYandex.ToLower(*c));
                prevWasLower = !isUpper;
            }
            str = result;
        }
    };

    class TSnakeToCamelCase : public NProtobufJson::IStringTransform {
        int GetType() const override {
            return 0;
        }

        void Transform(TString& str) const override {
            if (!str)
                return;
            TString result;
            TStringBuf s(str);
            TStringBuf tok;
            while (s.NextTok('_', tok))
                result.append(csYandex.ToUpper(tok[0])).append(tok.substr(1));
            str = result;
        }
    };

    void TransformJsonKeys(const NJson::TJsonValue& json, const NProtobufJson::IStringTransform& transform, NJson::TJsonValue& result) {
        if (json.IsMap()) {
            for (const auto& i : json.GetMap()) {
                TString key = i.first;
                transform.Transform(key);
                TransformJsonKeys(i.second, transform, result.InsertValue(key, NJson::JSON_NULL));
            }
        } else if (json.IsArray()) {
            for (const auto& i : json.GetArray())
                TransformJsonKeys(i, transform, result.AppendValue(NJson::JSON_NULL));
        } else
            result = json;
    }
    NDMInterface::TProject GetProject(IVersionedStorage& storage, const TString& name) {
        NDMInterface::TProject result;
        storage.GetValue("/projects/" + name, result);
        return result;
    }

    void Proto2Json(const google::protobuf::Message& proto, NJson::TJsonValue& json, bool skipMissing) {
        NProtobufJson::TProto2JsonConfig cfg;
        cfg.SetMissingSingleKeyMode(skipMissing ? NProtobufJson::TProto2JsonConfig::MissingKeySkip : NProtobufJson::TProto2JsonConfig::MissingKeyDefault);
        cfg.SetEnumMode(NProtobufJson::TProto2JsonConfig::EnumName);
        NJson::TJsonValue res;
        NProtobufJson::Proto2Json(proto, res, cfg);
        TransformJsonKeys(res, TCamelToSnakeCase(), json);
    }

    void Json2Proto(const NJson::TJsonValue& json, google::protobuf::Message& proto) {
        NJson::TJsonValue trasformed;
        TransformJsonKeys(json, TSnakeToCamelCase(), trasformed);
        NProtobufJson::Json2Proto(trasformed, proto);
    }

    TString GetConfigDir(const NDMInterface::TProject::TComponent& comp) {
        NRTYDeployInfo::IDeployComponentInfo::TPtr component(NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(comp.GetServiceType()));
        NRTYDeployInfo::IDeployServiceInfo::TPtr builder = component->BuildServiceInfo(comp.GetServiceName());
        return builder->GetConfigsPath();
    }

    void SaveFiles(NDMInterface::TProjectConfig::TFiles& result, IVersionedStorage& storage, const TString& path) {
        TVector<TString> files;
        if (!storage.GetNodes(path, files))
            return;
        for (const auto& file : files) {
            i64 ver = -1;
            if (!storage.GetVersion(path + "/" + file, ver) || ver < 0)
                continue;
            NDMInterface::TProjectConfig::TFiles::TFile& fileProto = *result.AddFile();
            fileProto.SetPath(path + "/" + file);
            fileProto.SetVersion(ver);
        }
    }

    void CreateProjectConfig(NDMInterface::TProjectConfig& result, IVersionedStorage& storage, const TString& project) {
        NDMInterface::TProject proj = GetProject(storage, project);
        result.SetTimestamp(Now().Seconds());
        result.SetName(project);
        SaveFiles(*result.MutableCommonFiles(), storage, "/common");
        SaveFiles(*result.MutableCommonFiles(), storage, "/defaults");
        for (size_t i = 0; i < proj.ComponentsSize(); ++i) {
            const NDMInterface::TProject::TComponent& comp = proj.GetComponents(i);
            NDMInterface::TProjectConfig::TComponent& compCfg = *result.AddComponents();
            *compCfg.MutableComponent() = comp;
            SaveFiles(*compCfg.MutableFiles(), storage, GetConfigDir(comp));
        }
    }

    void SaveConfigVersion(const TString& projectName, IVersionedStorage& storage, NJson::TJsonValue* report) {
        NDMInterface::TProjectConfig prjCfg;
        CreateProjectConfig(prjCfg, storage, projectName);
        TString path = "/projects/" + projectName + "/configs";
        storage.SetValue(path, prjCfg);
        i64 ver = -1;
        storage.GetVersion(path, ver);
        if (report) {
            Proto2Json(prjCfg, *report);
            report->InsertValue("version", ver);
        }
    }

}
