#include "alerts_logic.h"

#include <saas/deploy_manager/modules/golovan/format.h>
#include <saas/deploy_manager/modules/golovan/module.h>
#include <saas/deploy_manager/modules/resources/resources.h>
#include <saas/deploy_manager/scripts/common/alerts/remove.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/scripts/common/global_names/global_names.h>

using namespace NRTYAlerts;

bool VerifyCtypeService(const TString& service, const TString& ctype, const IDeployInfoRequest& request,
    bool& isMeta, TString& errorMessage) {
    NRTYDeployInfo::IDeployComponentInfo::TPtr info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(RTYSERVER_SERVICE);
    info->SetInfo(&request, ctype);
    if (info->SearchMap(service).GetInternalSearchMap().size()) {
        isMeta = false;
        return true;
    }
    info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(META_SERVICE_SERVICE);
    info->SetInfo(&request, ctype);
    if (info->SearchMap(service).GetInternalSearchMap().size()) {
        isMeta = true;
        return true;
    }
    errorMessage = "no such service for this ctype";
    return false;
}

bool GetCtypesServices(const IDeployInfoRequest& request, NJson::TJsonValue& result, const TString& ctypesPrefix) {
    const TVector<TString> comps({RTYSERVER_SERVICE, META_SERVICE_SERVICE});
    const TVector<TString>& ctypes = request.GetResourcesManager().GetCTypes();
    for (ui32 i = 0; i < ctypes.size(); ++i) {
        if (!!ctypesPrefix && !ctypes[i].StartsWith(ctypesPrefix)) {
            continue;
        }
        NJson::TJsonValue& services = result.InsertValue(ctypes[i], NJson::JSON_MAP);
        for (const auto& comp : comps) {
            NRTYDeployInfo::IDeployComponentInfo::TPtr info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(comp);
            info->SetInfo(&request, ctypes[i]);
            const NSearchMapParser::TSearchMap* ism;
            ism = &info->SearchMap();
            for (auto& service : ism->GetInternalSearchMap()) {
                services.InsertValue(service.Name, "");
            }
            for (auto& metaService : ism->GetMetaServices()) {
                services.InsertValue(metaService.Name, META_SERVICE_SERVICE);
            }
        }
    }
    return true;
}

bool ClearNotExistingServices(const IDeployInfoRequest& request, NJson::TJsonValue& result, bool dry) {
    if (!request.GetCommonData().GetConfig().GetAlertsConfig().GetEnabled()) {
            result["error"] = "calling ClearNotExistingServices with disabled alerts module";
            return false;
    }
    NJson::TJsonValue checks, alerts;
    if (!request.GetCommonData().GetJugglerModule().GetAllChecks(checks)) {
        result["error"] = "Cannot list checks: code=" + ToString(checks["code"].GetInteger()) + " error=" + checks["error"].GetString();
        return false;
    }
    if (!request.GetCommonData().GetGolovanModule().GetAlerts(TMap<TString, TString>(), alerts)) {
        result["error"] = "Cannot list alerts: code=" + ToString(alerts["code"].GetInteger()) + " error=" + alerts["error"].GetString();
        return false;
    }
    NJson::TJsonValue services(NJson::JSON_MAP);
    GetCtypesServices(request, services, request.GetCommonData().GetConfig().GetAlertsConfig().GetCtypesPrefix());
    result["present_services"] = services;
    ui16 oldCnt = 0, oldAlCnt=0;
    result["checks_to_remove"] = NJson::TJsonValue(NJson::JSON_ARRAY);
    for (const auto& golem : checks["content"].GetMap()) {
        for (const auto& jugId : golem.second.GetMap()) {
            TExistingCheck check(jugId.second);
            const TString& checkCtype = check.GetCtype();
            const TString& checkService = check.GetService();
            if (!services.Has(checkCtype)) {
                oldCnt++;
                NJson::TJsonValue& rm = result["checks_to_remove"].AppendValue(NJson::JSON_MAP);
                rm["juggler_id"] = jugId.first;
                rm["tag"] = "saas_ctype_" + checkCtype;
                continue;
            }
            if (!services[checkCtype].Has(checkService)) {
                oldCnt++;
                NJson::TJsonValue& rm = result["checks_to_remove"].AppendValue(NJson::JSON_MAP);
                rm["juggler_id"] = jugId.first;
                rm["tag"] = "saas_service_" + checkService;
                continue;
            }
        }
    }
    result["alerts_to_remove"] = NJson::TJsonValue(NJson::JSON_ARRAY);
    for (const auto& alert : alerts["content"]["response"]["result"].GetArray()) {
        const TExistingAlert exAlert(alert, request.GetCommonData().GetConfig().GetAlertsConfig().GetGolovanConfig().GetAlertsPrefix());
        const TString& alertCtype = exAlert.GetCtype();
        const TString& alertService = exAlert.GetService();
        if (!services.Has(alertCtype) || !services[alertCtype].Has(alertService)) {
            oldAlCnt++;
            result["alerts_to_remove"].AppendValue(exAlert.GetName());
            continue;
        }
    }
    result["cnt_alerts_to_remove"] = oldAlCnt;
    if (dry) {
        result["dry_run"] = true;
        return true;
    }

    bool juggRmSuccess = RemoveChecksList(request.GetCommonData(), result["checks_to_remove"], result.InsertValue("checks_removing", NJson::JSON_MAP));
    bool yasmRmSuccess = RemoveAlertsList(request.GetCommonData(), result["alerts_to_remove"], result.InsertValue("alerts_removing", NJson::JSON_MAP));
    if (!yasmRmSuccess || !juggRmSuccess) {
        result["error"] = "some old checks are not removed";
        return false;
    }
    return true;
}

TString GolovanId(const TString& service, const TString& ctype, const TString& aKey,
    const TString& itype, const TString& prefix, const TString& dcpart) {
    return prefix + "." + ctype + "." + service + "." + itype + dcpart + "." + aKey;
}

bool CompareSignals(const TString& signal1, const TString& signal2) {
    TString pureSignal1(signal1), pureSignal2(signal2);
    const TString unstableSymbols(" ()~");
    size_t pos;
    while ((pos = pureSignal1.find_first_of(unstableSymbols)) != TString::npos) {
        pureSignal1.replace(pos, 1, "");
    }
    while ((pos = pureSignal2.find_first_of(unstableSymbols)) != TString::npos) {
        pureSignal2.replace(pos, 1, "");
    }
    return pureSignal1 == pureSignal2;
}

TAlertsMaker::TAlertsMaker(const TString& service, const TString& ctype, IDeployInfoRequest& request)
    : Service(service)
    , Ctype(ctype)
    , GolovanPrefix(request.GetCommonData().GetConfig().GetAlertsConfig().GetGolovanConfig().GetAlertsPrefix())
    , IsMetaService(false)
    , Request(request)
    , Options(nullptr, nullptr)
    , Valid(false)
    , ErrorMessage("")
    {
    if (!request.GetCommonData().GetConfig().GetAlertsConfig().GetEnabled()) {
        ErrorMessage = "constructing AlertsMaker with disabled alerts module";
        return;
    }
    if (!ctype.StartsWith(request.GetCommonData().GetConfig().GetAlertsConfig().GetCtypesPrefix())) {
        ErrorMessage = "ctype '" + ctype + "' does not match filter from config, its alerts disabled";
        return;
    }
    if (!VerifyCtypeService(Service, Ctype, Request, IsMetaService, ErrorMessage)) {
        return;
    }
    TSimpleSharedPtr<NJson::TJsonValue> aKeys = new NJson::TJsonValue();
    TSimpleSharedPtr<NJson::TJsonValue> aSrvOpts = new NJson::TJsonValue();
    NJson::TJsonValue optsParts;
    if (!GetAlertsServiceOptions(Service, Ctype, Request, *aSrvOpts, *aKeys, optsParts)) {
        ErrorMessage = "Cannot get alerts service conf: " + (*aSrvOpts)["error"].GetString();
        return;
    }
    Options = TAlertsOptions(aKeys, aSrvOpts);
    Valid = true;
};

const TAlertsOptions& TAlertsMaker::GetOptions() const {
    return Options;
}

bool TAlertsMaker::GetExceedingMissingKeys(NJson::TJsonValue& result, NJson::TJsonValue& checks, NJson::TJsonValue& alerts) {

    TSet<TString> AKeys = Options.GetAKeys(IsMetaService);
    TSet<TString> JKeys;
    TSet<TString> JKeysUsers;
    TSet<TString> JKeysGolem;
    if (!Options.IsAllNotifyDisabled()) {
        for (const auto& akey : AKeys) {
            if (!Options.IsNotifyDisabled(akey)) {
                JKeys.insert(akey);
                if (Options.GetIsUsers(akey)) {
                    JKeysUsers.insert(akey);
                }
                if (Options.GetMustCall(akey)) {
                    JKeysGolem.insert(akey);
                }
            }
        }
    }

    NJson::TJsonValue& checksRes = result.InsertValue("checks", NJson::TJsonValue(NJson::JSON_MAP));
    checksRes["actual"] = NJson::TJsonValue(NJson::JSON_MAP);
    checksRes["exceeding"] = NJson::TJsonValue(NJson::JSON_ARRAY);
    checksRes["missing"] = NJson::TJsonValue(NJson::JSON_MAP);
    bool isChecksActual = true;

    TMap<TString, TString> filter = JugglerFilter(Service, Ctype, "");
    if (!Request.GetCommonData().GetJugglerModule().GetAllChecks(checks, filter)) {
        result = checks;
        result["stage_info"] = "error on checks getting";
        return false;
    }
    for (const auto& golem : checks["content"].GetMap()) {
        for (const auto& jugId : golem.second.GetMap()) {
            TExistingCheck check(jugId.second);
            const TString& checkKey = check.GetAKey();
            bool checkIsUsers = check.GetIsUsers();
            bool checkHasGolem = check.GetHasGolem();

            if (JKeys.contains(checkKey)) {
                if (checkIsUsers == JKeysUsers.contains(checkKey)
                    && checkHasGolem == JKeysGolem.contains(checkKey)) {
                    checksRes["actual"][checkKey] = "";
                } else {
                    NJson::TJsonValue& rm = checksRes["exceeding"].AppendValue(NJson::JSON_MAP);
                    rm["juggler_id"] = jugId.first;
                    rm["tag"] = check.GetIsUsersTag();
                    isChecksActual = false;
                }
            } else {
                NJson::TJsonValue& rm = checksRes["exceeding"].AppendValue(NJson::JSON_MAP);
                rm["juggler_id"] = jugId.first;
                rm["tag"] = "saas_service_" + Service;
                isChecksActual = false;
            }
        }
    }
    for (const auto& akey : JKeys) {
        if (!checksRes["actual"].Has(akey)) {
            checksRes["missing"][akey] = "";
            isChecksActual = false;
        }
    }
    checksRes["is_actual"] = isChecksActual;

    NJson::TJsonValue& alertsRes = result.InsertValue("alerts", NJson::JSON_MAP);
    alertsRes["actual"] = NJson::TJsonValue(NJson::JSON_MAP);
    alertsRes["exceeding"] = NJson::TJsonValue(NJson::JSON_ARRAY);
    alertsRes["missing"] = NJson::TJsonValue(NJson::JSON_MAP);
    bool isAlertsActual = true;

    if (!Request.GetCommonData().GetGolovanModule().GetAlerts(filter, alerts)) {
        alertsRes = alerts;
        result["stage_info"] = "error on golovan alerts getting";
        return false;
    }
    for (const auto& alert : alerts["content"]["response"]["result"].GetArray()) {
        const TExistingAlert exAlert(alert, Request.GetCommonData().GetConfig().GetAlertsConfig().GetGolovanConfig().GetAlertsPrefix());
        const TString& alertKey = exAlert.GetAKey();
        if (AKeys.contains(alertKey)) {
            alertsRes["actual"][alertKey] = "";
        } else {
            alertsRes["exceeding"].AppendValue(exAlert.GetName());
            isAlertsActual = false;
        }
    }
    for (const auto& akey : AKeys) {
        if (!alertsRes["actual"].Has(akey)) {
            alertsRes["missing"][akey] = "";
            isAlertsActual = false;
        }
    }
    alertsRes["is_actual"] = isAlertsActual;
    result["is_actual"] = isChecksActual && isAlertsActual;
    return true;
}

bool TAlertsMaker::CompareSingleCheck(const TString& aKey, const TExistingCheck& exCheck, bool needOwnersCheck, NJson::TJsonValue& result) {
    if (exCheck.GetAKey() != aKey) {
        result["error"] = "internal error, akeys are not the same";
        return false;
    }
    bool isActual = true;
    ui32 exCrit, cfCrit, exStab, cfStab, exBoost, cfBoost;
    exCheck.GetFlaps(exCrit, exStab, exBoost);
    Options.GetFlaps(aKey, cfCrit, cfStab, cfBoost);
    if (exCrit != cfCrit || exStab != cfStab || exBoost != cfBoost) {
        isActual = false;
        result["info"] = NJson::TJsonValue(NJson::JSON_MAP);
        NJson::TJsonValue& flaps = result["info"].InsertValue("flaps", NJson::JSON_MAP);
        flaps["existing"] = NJson::TJsonValue(NJson::JSON_MAP);
        flaps["existing"]["critical_time"] = exCrit;
        flaps["existing"]["stable_time"] = exStab;
        flaps["existing"]["boost_time"] = exBoost;
        flaps["conf"] = NJson::TJsonValue(NJson::JSON_MAP);
        flaps["conf"]["critical_time"] = cfCrit;
        flaps["conf"]["stable_time"] = cfStab;
        flaps["conf"]["boost_time"] = cfBoost;
    }
    bool mustCall = Options.GetMustCall(aKey);
    TString exGolemStart, cfGolemStart, exGolemEnd, cfGolemEnd;
    if (mustCall) {
        Options.GetNotificationTimes(aKey, cfGolemStart, cfGolemEnd);
        if (!exCheck.GetGolemTimes(exGolemStart, exGolemEnd)) {
            result["error"] = "cannot get golem times";
            return false;
        }
    }
    if (mustCall && (exGolemStart != cfGolemStart || exGolemEnd != cfGolemEnd)) {
        isActual = false;
        if (!result.Has("info")) {
            result["info"] = NJson::TJsonValue(NJson::JSON_MAP);
        }
        NJson::TJsonValue& times = result["info"].InsertValue("phone_times", NJson::JSON_MAP);
        times["existing"] = NJson::TJsonValue(NJson::JSON_MAP);
        times["existing"]["time_start"] = exGolemStart;
        times["existing"]["time_end"] = exGolemEnd;
        times["conf"] = NJson::TJsonValue(NJson::JSON_MAP);
        times["conf"]["time_start"] = cfGolemStart;
        times["conf"]["time_end"] = cfGolemEnd;
    }

    bool isUsers = Options.GetIsUsers(aKey);
    if (mustCall && needOwnersCheck) {
        TVector<TString> exOwners;
        TVector<TString> cfOwners;
        exCheck.GetOwners(exOwners);
        Options.GetOwners(isUsers, cfOwners);
        if (exOwners != cfOwners) {
            isActual = false;
            if (!result.Has("info")) {
                result["info"] = NJson::TJsonValue(NJson::JSON_MAP);
            }
            NJson::TJsonValue& owners = result["info"].InsertValue("owners", NJson::JSON_MAP);
            owners["existing"] = NJson::TJsonValue(NJson::JSON_ARRAY);
            for (const auto& owner : exOwners) {
                owners["existing"].AppendValue(owner);
            }
            owners["conf"] = NJson::TJsonValue(NJson::JSON_ARRAY);
            for (const auto& owner : cfOwners) {
                owners["conf"].AppendValue(owner);
            }
        }
    }

    if (isUsers) {
        TVector<TString> exUsers;
        TVector<TString> cfUsers;
        exCheck.GetWarnUsers(exUsers);
        Options.GetOwners(true, cfUsers);

        if (exUsers != cfUsers) {
            isActual = false;
            if (!result.Has("info")) {
                result["info"] = NJson::TJsonValue(NJson::JSON_MAP);
            }
            NJson::TJsonValue& users = result["info"].InsertValue("warn_users", NJson::JSON_MAP);
            users["existing"] = NJson::TJsonValue(NJson::JSON_ARRAY);
            for (const auto& user : exUsers) {
                users["existing"].AppendValue(user);
            }
            users["conf"] = NJson::TJsonValue(NJson::JSON_ARRAY);
            for (const auto& user : cfUsers) {
                users["conf"].AppendValue(user);
            }
        }
    }
    result["is_actual"] = isActual;
    return true;
}

bool TAlertsMaker::GetUnactualDataChecks(const TSet<TString>& aKeys, const NJson::TJsonValue& checks, NJson::TJsonValue& result) {
    NJson::TJsonValue& checksActual = result.InsertValue("actual", NJson::JSON_MAP);
    NJson::TJsonValue& checksMismatch = result.InsertValue("mismatching", NJson::JSON_MAP);
    NJson::TJsonValue& errors = result.InsertValue("errors", NJson::JSON_MAP);

    bool needHostsOwnersCheck = !Request.GetCommonData().GetConfig().GetAlertsConfig().GetJugglerConfig().GetIsTesting();
    bool isDataActual = true;

    ui16 errCnt = 0;
    for (const auto& golem : checks["content"].GetMap()) {
        for (const auto& jugId : golem.second.GetMap()) {
            TExistingCheck check(jugId.second);
            const TString& checkKey = check.GetAKey();
            if (!aKeys.contains(checkKey)) {
                continue;
            }
            NJson::TJsonValue singleRes;
            bool singleSuccess = CompareSingleCheck(checkKey, check, needHostsOwnersCheck, singleRes);
            if (!singleSuccess) {
                ++errCnt;
                errors[checkKey] = singleRes;
                isDataActual = false;
            } else if (singleRes["is_actual"].GetBoolean()) {
                checksActual[checkKey] = "";
            } else {
                checksMismatch[checkKey] = singleRes["info"];
                isDataActual = false;
            }
        }
    }
    result["is_data_actual"] = isDataActual;
    result["is_actual"] = isDataActual;
    if (errCnt) {
        result["error"] = "some comparings not succeeded";
    }
    return !errCnt;
}

bool TAlertsMaker::CompareSingleAlert(const TString& aKey, const TExistingAlert& exAlert, NJson::TJsonValue& result) {
    if (exAlert.GetAKey() != aKey) {
        result["error"] = "internal error, akeys are not the same";
        return false;
    }
    double exCrit, cfCrit, exWarn, cfWarn;
    ui16 exAvgsec, cfAvgsec;
    bool isActual = true;
    TString message;
    if (!exAlert.GetLimits(exWarn, exCrit, exAvgsec, message)) {
        result["error"] = message;
        return false;
    }
    if (!Options.GetLimits(aKey, cfWarn, cfCrit, cfAvgsec)) {
        result["error"] = "cannot get conf limits";
        return false;
    }
    double eps = 0.0000001;
    if (abs(exCrit - cfCrit) > eps || abs(exWarn - cfWarn) > eps ) {
        isActual = false;
    }
    if (exAvgsec != cfAvgsec) {
        if (exAvgsec > 10 || cfAvgsec > 10) {
            isActual = false;
        }
    }
    if (!isActual) {
        NJson::TJsonValue& limits = result.InsertValue("info", NJson::JSON_MAP).InsertValue("limits", NJson::JSON_MAP);
        limits["existing"] = NJson::TJsonValue(NJson::JSON_MAP);
        limits["existing"]["warn"] = exWarn;
        limits["existing"]["crit"] = exCrit;
        limits["existing"]["avgsec"] = exAvgsec;
        limits["conf"] = NJson::TJsonValue(NJson::JSON_MAP);
        limits["conf"]["warn"] = cfWarn;
        limits["conf"]["crit"] = cfCrit;
        limits["conf"]["avgsec"] = cfAvgsec;
    }
    const TString& signalEx = exAlert.GetSignal();
    TString signalConf = GetSignal(aKey);
    if (!CompareSignals(signalEx, signalConf)) {
        isActual = false;
        if (!result.Has("info")) {
            result.InsertValue("info", NJson::JSON_MAP);
        }
        NJson::TJsonValue& signal = result["info"].InsertValue("signal", NJson::JSON_MAP);
        signal["existing"] = signalEx;
        signal["conf"] = signalConf;
    }
    result["is_actual"] = isActual;
    return true;
}

bool TAlertsMaker::GetUnactualDataAlerts(const TSet<TString>& aKeys, const NJson::TJsonValue& alerts, NJson::TJsonValue& result) {
    NJson::TJsonValue& actuals = result.InsertValue("actual", NJson::JSON_MAP);
    NJson::TJsonValue& mismatch = result.InsertValue("mismatching", NJson::JSON_MAP);
    NJson::TJsonValue& errors = result.InsertValue("errors", NJson::JSON_MAP);

    ui16 unactCnt = 0;
    for (const auto& alert : alerts["content"]["response"]["result"].GetArray()) {
        const TExistingAlert exAlert(alert, Request.GetCommonData().GetConfig().GetAlertsConfig().GetGolovanConfig().GetAlertsPrefix());
        const TString& alertKey = exAlert.GetAKey();
        if (!aKeys.contains(alertKey)) {
            continue;
        }
        NJson::TJsonValue singleRes;
        bool singleSuccess = CompareSingleAlert(alertKey, exAlert, singleRes);
        if (!singleSuccess) {
            errors[alertKey] = singleRes;
            ++unactCnt;
            continue;
        }
        if (singleRes["is_actual"].GetBoolean()) {
            actuals[alertKey] = "";
        } else {
            mismatch[alertKey] = singleRes["info"];
            ++unactCnt;
        }
    }
    result["is_actual"] = !unactCnt;
    if (errors.GetMap().ysize()) {
        result["error"] = "some comparings not succeeded";
        return false;
    }
    return true;
}

bool TAlertsMaker::GetFullServiceActuality(NJson::TJsonValue& result) {
    NJson::TJsonValue& presence = result.InsertValue("presence", NJson::JSON_MAP);
    NJson::TJsonValue checks, alerts;
    if (!GetExceedingMissingKeys(presence, checks, alerts)) {
        result["error"] = "error on GetExceedingMissingKeys";
        return false;
    }
    bool isListActual = presence["is_actual"].GetBoolean();

    TSet<TString> actCheckKeys;
    for (const auto& ak : presence["checks"]["actual"].GetMap()) {
        actCheckKeys.insert(ak.first);
    }
    NJson::TJsonValue& checksRes = result.InsertValue("checks", NJson::JSON_MAP);
    bool juggSuccess = GetUnactualDataChecks(actCheckKeys, checks, checksRes);

    TSet<TString> actAlertKeys;
    for (const auto& ak : presence["alerts"]["actual"].GetMap()) {
        actAlertKeys.insert(ak.first);
    }
    NJson::TJsonValue& alertsRes = result.InsertValue("alerts", NJson::JSON_MAP);
    bool yasmSuccess = GetUnactualDataAlerts(actAlertKeys, alerts, alertsRes);

    bool isActual = isListActual && checksRes["is_actual"].GetBoolean() && alertsRes["is_actual"].GetBoolean();
    result["is_actual"] = isActual;
    bool success = juggSuccess && yasmSuccess;
    result["success"] = success;
    return success;
}

bool TAlertsMaker::FillJugglerCheck(const TString& aKey, TJugglerCheck& jCheck, const TVector<TString>& owners) {
    if (!Options.GetAKeys(IsMetaService).contains(aKey)) {
        ErrorMessage = aKey + " alert key is disabled for this service";
        return false;
    }
    if (Options.IsAllNotifyDisabled() || Options.IsNotifyDisabled(aKey)) {
        ErrorMessage = aKey + " alert key notifications disabled";
        return false;
    }
    jCheck.SetGolovanId(GolovanId(Service, Ctype, aKey, Options.GetIType(aKey), GolovanPrefix));
    bool isUsers = Options.GetIsUsers(aKey);
    jCheck.SetIsUsers(isUsers);
    ui32 criticalTime, stableTime, boostTime;
    Options.GetFlaps(aKey, criticalTime, stableTime, boostTime);
    jCheck.SetFlaps(criticalTime, stableTime, boostTime);

    TString phoneStartTime, phoneEndTime;
    Options.GetNotificationTimes(aKey, phoneStartTime, phoneEndTime);
    jCheck.SetNotifications(phoneStartTime, phoneEndTime, 0, TSet<TString>());
    jCheck.SetOwners(owners);

    bool mustCall = Options.GetMustCall(aKey);
    jCheck.SetSkipGolem(!mustCall);
    if (isUsers) {
        jCheck.SetWarnUsers(owners);
    }
    return true;
}

TString TAlertsMaker::GetSignal(const TString& aKey) const {
    TString templ = Options.GetSignalTempl(aKey);
    const TString SERV = "{service}";
    const TString CTYPE = "{ctype}";
    size_t pos;
    while ((pos = templ.find(SERV)) != TString::npos) {
        templ.replace(pos, SERV.size(), Service);
    }
    while ((pos = templ.find(CTYPE)) != TString::npos) {
        templ.replace(pos, CTYPE.size(), Ctype);
    }
    return templ;
}

bool TAlertsMaker::ComputePrjTag(TString& prj) const {
    const TString toReplace = "stable";
    if (Ctype.StartsWith(toReplace)) {
        prj = Ctype;
        prj.replace(0, toReplace.size(), "saas");
    } else {
        return false;
    }
    prj += "-" + Service;
    size_t pos;
    while ((pos = prj.find("_")) != TString::npos) {
        prj.replace(pos, 1, "-");
    }
    return true;
}

TYasmAlert TAlertsMaker::GenerateYasmAlert(const TString& aKey, bool& success) {
    const TString itype = Options.GetIType(aKey);
    const TString golovanId = GolovanId(Service, Ctype, aKey, itype, GolovanPrefix);
    TString signal = GetSignal(aKey);
    TYasmAlert alert(golovanId, signal, itype);
    if (Options.GetWithPrj(aKey)) {
        TString prj;
        if (ComputePrjTag(prj)) {
            alert.AddRealTag("prj", prj);
        } else {
            success = false;
            ErrorMessage = "no prj convertion rule for ctype '" + Ctype + "', key: " + aKey;
            return alert;
        }
    }
    if (Options.GetWithTier(aKey)) {
        alert.AddRealTag("tier", Service);
    }

    double warn, crit;
    ui16 avgsec;
    if (!Options.GetLimits(aKey, warn, crit, avgsec)) {
        ErrorMessage = "cannot get alert limits";
        success = false;
        return alert;
    }
    alert.SetLimits(warn, crit, avgsec);
    success = true;
    return alert;
}

bool TAlertsMaker::CreateYasmAlert(const TString& aKey, NJson::TJsonValue& result) {
    bool genSuccess = false;
    TYasmAlert alert = GenerateYasmAlert(aKey, genSuccess);
    if (genSuccess) {
        bool success = Request.GetCommonData().GetGolovanModule().CreateSingleAlert(alert, result);
        return success;
    } else {
        result["error"] = ErrorMessage;
        result["partial_alert"] = alert.ToJson();
        return false;
    }
}

bool TAlertsMaker::CreateJugglerCheck(const TString& aKey, NJson::TJsonValue& result, bool dryRun) {
    bool success = false;
    TJugglerCheck check(Service, Ctype, aKey, false,
        Request.GetCommonData().GetConfig().GetAlertsConfig().GetJugglerConfig().GetCommonTag(),
        Request.GetCommonData().GetConfig().GetAlertsConfig().GetJugglerConfig().GetNamespace(),
        GOLEM_DEFAULT_PREFIX);

    TVector<TString> owners;
    Options.GetOwners(aKey, owners);
    if (!FillJugglerCheck(aKey, check, owners)) {
        success = false;
        result["error"] = "Cannot fill juggler check: " + ErrorMessage;
    } else {
        if (dryRun) {
            result["juggler_check"] = check.ToJson();
            result["dryRun"] = true;
            result["owners"] = JoinStrings(owners, ",");
            success = true;
        } else {
            success = Request.GetCommonData().GetJugglerModule().CreateSingleCheck(check, owners, result);
        }
    }
    return success;
}

bool TAlertsMaker::ActualizeExceedingMissingKeys(const NJson::TJsonValue& lists, NJson::TJsonValue& result) {
    bool rmSuccess = true;
    if (lists["checks"]["exceeding"].GetArray().ysize()) {
        rmSuccess = RemoveChecksList(Request.GetCommonData(), lists["checks"]["exceeding"],
        result.InsertValue("checks_removing", NJson::JSON_MAP));
    }
    if (rmSuccess && lists["alerts"]["exceeding"].GetArray().ysize()) {
        rmSuccess = RemoveAlertsList(Request.GetCommonData(), lists["alerts"]["exceeding"],
        result.InsertValue("alerts_removing", NJson::JSON_MAP));
    }
    bool crSuccess = true;
    if (lists["alerts"]["missing"].GetMap().ysize()) {
        ui16 failCnt = 0;
        result.InsertValue("alerts_creating", NJson::JSON_MAP);
        NJson::TJsonValue& aCreate = result["alerts_creating"].InsertValue("created", NJson::JSON_ARRAY);
        for (const auto& ak : lists["alerts"]["missing"].GetMap()) {
            bool singleSuccess = CreateYasmAlert(ak.first, aCreate.AppendValue(NJson::JSON_MAP));
            if (!singleSuccess) {
                if (++failCnt > 2) {
                    break;
                }
            }
        }
        if (failCnt) {
            crSuccess = false;
            result["alerts_creating"]["fails_cnt"] = failCnt;
        }
    }
    if (crSuccess && lists["checks"]["missing"].GetMap().ysize()) {
        result["checks_creating"] = NJson::TJsonValue(NJson::JSON_MAP);
        NJson::TJsonValue& cCreate = result["checks_creating"].InsertValue("created", NJson::JSON_ARRAY);
        ui16 failCnt = 0;
        for (const auto& c : lists["checks"]["missing"].GetMap()) {
            NJson::TJsonValue& singleRes = cCreate.AppendValue(NJson::JSON_MAP);
            bool singleSuccess = CreateJugglerCheck(c.first, singleRes);
            if (!singleSuccess) {
                if (++failCnt > 2) {
                    break;
                }
            }
        }
        if (failCnt) {
            crSuccess = false;
            result["checks_creating"]["fails_cnt"] = failCnt;
        }
    }
    return rmSuccess && crSuccess;
}

bool TAlertsMaker::ActualizeChecks(const NJson::TJsonValue& cDiff, NJson::TJsonValue& result) {
    if (cDiff["is_actual"].GetBoolean()) {
        return true;
    }
    bool success = true;
    NJson::TJsonValue& updated = result.InsertValue("updated", NJson::JSON_ARRAY);

    ui16 cntChanged = 0, failCnt = 0;
    for (const auto& c : cDiff["mismatching"].GetMap()) {
        NJson::TJsonValue& singleRes = updated.AppendValue(NJson::JSON_MAP);
        bool singleSuccess = CreateJugglerCheck(c.first, singleRes);
        if (singleSuccess) {
            ++cntChanged;
        } else {
            if (++failCnt > 2) {
                break;
            }
        }
    }
    success = success && !failCnt;
    result["cnt_changed"] = cntChanged;
    result["fails_cnt"] = failCnt;
    result["success"] = success;
    return success;
}

bool TAlertsMaker::ActualizeAlerts(const NJson::TJsonValue& aDiff, NJson::TJsonValue& result) {
    if (aDiff["is_actual"].GetBoolean()) {
        return true;
    }
    ui16 cntChanged = 0, failCnt = 0;
    NJson::TJsonValue& updated = result.InsertValue("updated", NJson::JSON_ARRAY);
    for (const auto& ak : aDiff["mismatching"].GetMap()) {
        bool singleSuccess = CreateYasmAlert(ak.first, updated.AppendValue(NJson::JSON_MAP));
        if (singleSuccess) {
            ++cntChanged;
        } else {
            if (++failCnt > 2) {
                break;
            }
        }
    }
    result["cnt_changed"] = cntChanged;
    result["fails_cnt"] = failCnt;
    result["success"] = !failCnt;
    return !failCnt;
}

bool TAlertsMaker::ActualizeFullService(bool existingOnly, NJson::TJsonValue& result, bool dryRun) {
    NJson::TJsonValue& state = result.InsertValue("actuality", NJson::JSON_MAP);
    bool stateSuccess = GetFullServiceActuality(state);
    if (!stateSuccess) {
        result["error"] = "errors in getting actuality";
        return false;
    }
    if (dryRun) {
        result["dry_run"] = true;
        return true;
    }
    if (!state.Has("presence") || !state.Has("alerts") || !state.Has("checks") || !state.Has("is_actual")) {
        result["error"] = "internal error: some fields in state info missing, stop processing";
        return false;
    }
    NJson::TJsonValue& addRmDone = result.InsertValue("done_presence", NJson::JSON_MAP);
    bool addRmSuccess = true;
    if (!existingOnly) {
        addRmSuccess = ActualizeExceedingMissingKeys(state["presence"], addRmDone);
    } else {
        addRmDone["skipped"] = true;
    }

    NJson::TJsonValue& checksDone = result.InsertValue("done_checks", NJson::JSON_MAP);
    bool juggSuccess = ActualizeChecks(state["checks"], checksDone);

    NJson::TJsonValue& alertsDone = result.InsertValue("done_alerts", NJson::JSON_MAP);
    bool yasmSuccess = ActualizeAlerts(state["alerts"], alertsDone);

    bool success = addRmSuccess && juggSuccess && yasmSuccess;
    result["success"] = success;
    return success;
}
