
#include "alerts_config.h"

#include <saas/deploy_manager/protos/sla_description.pb.h>
#include <saas/deploy_manager/scripts/common/sla_processing/sla_processing.h>

using namespace NRTYDeploy;

namespace {
bool GetStorageFileJson(const TString& path, const IDeployInfoRequest& request, NJson::TJsonValue& result) {
    TString content;
    if (!request.GetStorage().GetValue(path, content)) {
        result["error"] = "cannot get file from storage, path=" + path;
        return false;
    }
    TStringInput si(content);
    if (!NJson::ReadJsonTree(&si, &result)) {
        result["error"] = "cannot parse json, path=" + path;
        return false;
    }
    return true;
}

bool HasSla(const NJson::TJsonValue& keyOptions, const NJson::TJsonValue& sla) {
    if (!keyOptions.Has("sla_field_crit")) {
        return false;
    }
    const TString& slaFieldCrit = keyOptions["sla_field_crit"].GetString();
    return sla.Has(slaFieldCrit);
}

bool TransformWithSla(const NJson::TJsonValue& keyOptions, const NJson::TJsonValue& sla, NJson::TJsonValue& limits) {
    const double& koeff = keyOptions.Has("sla_koeff") ? keyOptions["sla_koeff"].GetDouble() : 1;
    const TString& slaFieldCrit = keyOptions["sla_field_crit"].GetString();
    if (sla.Has(slaFieldCrit)) {
        limits["crit"] = sla[slaFieldCrit].GetDouble() * koeff;
        limits["from_sla"] = true;
    }

    const TString& slaFieldWarn = keyOptions["sla_field_warn"].GetString();
    if (sla.Has(slaFieldWarn)) {
        limits["warn"] = sla[slaFieldWarn].GetDouble() * koeff;
    } else if (keyOptions.Has("crit2warn_koeff") && sla.Has(slaFieldCrit)) {
        const double crit2warn = keyOptions["crit2warn_koeff"].GetDouble();
        limits["warn"] = sla[slaFieldCrit].GetDouble() * koeff * crit2warn;
    }
    return true;
}

bool JoinAlertsConf(const NJson::TJsonValue& defaultConf, const NJson::TJsonValue& serviceConf, const NJson::TJsonValue& slaConf,
    const NJson::TJsonValue& keysConf, NJson::TJsonValue& result) {

    result = defaultConf;
    if (!result.Has("data")) {
        result["data"] = NJson::TJsonValue(NJson::JSON_MAP);
    }
    for (const auto& i : serviceConf.GetMap()) {
        if (i.first != "data") {
            result[i.first] = i.second;
        } else {
            for (const auto& akey : i.second.GetMap()) {
                if (!result["data"].Has(akey.first)) {
                    result["data"][akey.first] = akey.second;
                } else {
                    for (const auto& field : akey.second.GetMap()) {
                        result["data"][akey.first][field.first] = field.second;
                    }
                }
            }
        }
    }
    TSet<TString> siglist;
    for (const auto& i : result["default_signals"].GetArray()) {
        siglist.insert(i.GetString());
    }
    for (const auto& i : result["add_signals"].GetArray()) {
        siglist.insert(i.GetString());
    }
    for (const auto& i : result["if_sla_signals"].GetArray()) {
        if (HasSla(keysConf[i.GetString()], slaConf)) {
            siglist.insert(i.GetString());
            if (!result["data"].Has(i.GetString())) {
                result["data"][i.GetString()] = NJson::TJsonValue(NJson::JSON_MAP);
            }
        }
    }
    result["all_signals"] = NJson::TJsonValue(NJson::JSON_ARRAY);
    for (const auto& sig : siglist) {
        result["all_signals"].AppendValue(sig);
    }

    for (auto& akey : result["data"].GetMap()) {
        if (keysConf[akey.first].Has("sla_field_crit")) {
            result["data"][akey.first]["should_sla"] = true;
            if (!akey.second.Has("limits")) {
                result["data"][akey.first].InsertValue("limits", NJson::JSON_MAP);
            }
            if (slaConf.GetMap().ysize()) {
                TransformWithSla(keysConf[akey.first], slaConf, result["data"][akey.first]["limits"]);
            }
        }
    }
    if (slaConf.Has("responsibles") && slaConf["responsibles"].GetArray().ysize()) {
        result["user_resps"] = NJson::TJsonValue(NJson::JSON_ARRAY);
        result["user_resps_from_sla"] = true;
        for (const auto& r : slaConf["responsibles"].GetArray()) {
            TString login = r.GetString();
            size_t dogPos = login.find("@");
            if (dogPos != TString::npos) {
                login = login.substr(0, dogPos);
            }
            result["user_resps"].AppendValue(login);
        }
    }

    return true;
}
}

NJson::TJsonValue GetAlertKeys(const IDeployInfoRequest& request) {
    NJson::TJsonValue alertKeys;
    if (!GetStorageFileJson("/configs/alert_keys.json", request, alertKeys)) {
        ythrow yexception() << "cannot get alerts keys list, error: " << alertKeys["error"];
    }
    if (!alertKeys.Has("keys")) {
        ythrow yexception() << "incorrect keys format, no keys field";
    }
    return alertKeys["keys"];
}

bool GetAlertsServiceOptions(const TString& service, const TString& ctype, const IDeployInfoRequest& request,
    NJson::TJsonValue& result, NJson::TJsonValue& aKeys, NJson::TJsonValue& resultParts) {

    TString confStr;
    NJson::TJsonValue& defaultConf = resultParts.InsertValue("default_conf", NJson::JSON_MAP);
    NJson::TJsonValue& serviceConf = resultParts.InsertValue("service_conf", NJson::JSON_MAP);
    NJson::TJsonValue& slaConf = resultParts.InsertValue("sla_conf", NJson::JSON_MAP);

    aKeys = GetAlertKeys(request);
    if (!GetStorageFileJson("/defaults/alerts.conf", request, defaultConf)) {
        result = defaultConf;
        return false;
    }
    TString patchPath = "/configs/" + service + "/alerts.conf";
    bool hasServiceConf = request.GetStorage().ExistsNode(patchPath);
    if (hasServiceConf && !GetStorageFileJson(patchPath, request, serviceConf)) {
        result = serviceConf;
        return false;
    }

    if (!ctype || !ctype.StartsWith(request.GetCommonData().GetConfig().GetAlertsConfig().GetCtypesPrefix())) {
        slaConf["conf_is_partial"] = "sla_omitted";
    } else {
        NSaasProto::TSlaDescription slaDescr;
        if (!TryGetSLADescription(service, request.GetStorage(), slaDescr)) {
            result["error"] = "cannot get sla";
            return false;
        }
        slaConf = SerializeDescription(slaDescr, ctype);
    }
    return JoinAlertsConf(defaultConf, serviceConf, slaConf, aKeys, result);
}

TAlertsOptions::TAlertsOptions(const TSimpleSharedPtr<NJson::TJsonValue>& aKeys, const TSimpleSharedPtr<NJson::TJsonValue>& alertsConf)
    : AKeys(aKeys)
    , AlertsConf(alertsConf) {
    if (!aKeys) {
        AKeys = MakeSimpleShared<NJson::TJsonValue>();
    }
    if (!alertsConf) {
        AlertsConf = MakeSimpleShared<NJson::TJsonValue>();
    }
};

TSet<TString> TAlertsOptions::GetAKeys(const bool isMetaService) const {
    TSet<TString> result;
    for (const auto& i : (*AlertsConf)["all_signals"].GetArray()) {
        if (!isMetaService || !(*AKeys)[i.GetString()]["exclude_meta"].GetBooleanRobust()) {
            result.insert(i.GetString());
        }
    }
    return result;
}

TString TAlertsOptions::GetIType(const TString& aKey) const {
    return (*AKeys)[aKey]["itype"].GetString();
}

bool TAlertsOptions::GetWithPrj(const TString& aKey) const {
    return (*AKeys)[aKey]["with_prj"].GetBooleanRobust();
}

bool TAlertsOptions::GetWithTier(const TString& aKey) const {
    return (*AKeys)[aKey]["with_tier"].GetBooleanRobust();
}

bool TAlertsOptions::GetIsUsers(const TString& aKey) const {
    return (*AlertsConf)["data"][aKey]["is_users"].GetBooleanRobust();
}

bool TAlertsOptions::GetMustCall(const TString& aKey) const {
    if ((*AlertsConf)["data"][aKey].Has("must_call")) {
        return (*AlertsConf)["data"][aKey]["must_call"].GetBooleanRobust();
    }
     return !GetIsUsers(aKey);
}

bool TAlertsOptions::IsNotifyDisabled(const TString& aKey) const {
    return (*AlertsConf)["data"][aKey]["disable_notify"].GetBooleanRobust();
}

bool TAlertsOptions::IsAllNotifyDisabled() const {
    return (*AlertsConf)["disable_notify"].GetBooleanRobust();
}

bool TAlertsOptions::GetFlaps(const TString& aKey, ui32& critical, ui32& stable, ui32& boost) const {
    const NJson::TJsonValue& conf = (*AlertsConf)["data"][aKey];
    const NJson::TJsonValue& flaps = conf.Has("flaps") ? conf["flaps"] : (*AlertsConf)["default_flaps"];
    critical = flaps["critical_time"].GetUInteger();
    stable = flaps["stable_time"].GetUInteger();
    boost = flaps["boost_time"].GetUInteger();
    return true;
}

bool TAlertsOptions::GetNotificationTimes(const TString& aKey, TString& timeStart, TString& timeEnd) const {
    const NJson::TJsonValue& conf = (*AlertsConf)["data"][aKey];
    const NJson::TJsonValue& times = conf.Has("phone_times") ? conf["phone_times"] : (*AlertsConf)["default_phone_times"];
    timeStart = times["time_start"].GetString();
    timeEnd = times["time_end"].GetString();
    return true;
}

bool TAlertsOptions::GetOwners(const bool isUsers, TVector<TString>& result) const {
    const NJson::TJsonValue& owners = isUsers ? (*AlertsConf)["user_resps"] : (*AlertsConf)["default_resps"];
    const TString ROBOT_NAME = "saas-robot";
    bool hasRobot = false;
    for (const auto& i : owners.GetArray()) {
        result.push_back(i.GetString());
        if (i.GetString() == ROBOT_NAME) {
            hasRobot = true;
        }
    }
    if (!hasRobot) {
        result.push_back(ROBOT_NAME);
    }
    return true;
}

bool TAlertsOptions::GetOwners(const TString& aKey, TVector<TString>& result) const {
    bool isUsers = GetIsUsers(aKey);
    return GetOwners(isUsers, result);
}

TString TAlertsOptions::GetSignalTempl(const TString& aKey) const {
    return (*AKeys)[aKey]["templ"].GetString();
}

bool TAlertsOptions::GetLimits(const TString& aKey, double& warn, double& crit, ui16& avgsec) const {
    const NJson::TJsonValue& conf = (*AlertsConf)["data"][aKey];
    if (!conf.Has("limits")) {
        return false;
    }
    const NJson::TJsonValue& limits = conf["limits"];
    warn = limits["warn"].GetDouble();
    crit = limits["crit"].GetDouble();
    avgsec = limits["avgsec"].GetUInteger();
    return true;
}
