
#include "script.h"

#include <saas/deploy_manager/protos/sla_description.pb.h>
#include <saas/deploy_manager/server/client/client.h>
#include <saas/deploy_manager/scripts/common/sla_processing/sla_processing.h>
#include <saas/deploy_manager/scripts/process_alerts/alerts_logic.h>

#include <library/cpp/digest/md5/md5.h>

#include <util/string/type.h>

namespace {
    void ReportError(IDeployInfoRequest& request, const TString& message, ui16 code = 400) {
        request.Output() << "HTTP/1.1 " << code << " \r\n\r\n";
        request.Output() << message;
    }

    TMaybe<TString> CheckNonEmptyArray(const NJson::TJsonValue& value, const TString& name, const bool required) {
        if (!value.Has(name)) {
            return required ? TMaybe<TString>("Missing " + name) : Nothing();
        } else if (!value[name].IsArray() || value[name].GetArray().size() == 0) {
            return "Wrong " + name;
        }
        return Nothing();
    }

    TMaybe<NJson::TJsonValue> ParseInput(IDeployInfoRequest& request, const bool usedForSet) {
        const TBlob& fileData = request.GetBlob();
        DEBUG_LOG << "POST data size = " << fileData.Length() << Endl;
        const TStringBuf newData(fileData.AsCharPtr(), fileData.Length());

        NJson::TJsonValue result;
        if (!NJson::ReadJsonFastTree(newData, &result)) {
            ReportError(request, TString("Failed to parse JSON: ") + newData);
            return Nothing();
        }

        if (!result.IsMap()) {
            ReportError(request, "JSON is not map");
            return Nothing();
        }

        TMaybe<TString> errorMaybe = CheckNonEmptyArray(result, "owners", usedForSet);

        if (errorMaybe.Empty()) {
            errorMaybe = CheckNonEmptyArray(result, "responsibles", usedForSet);
        }

        if (errorMaybe.Defined()) {
            ReportError(request, errorMaybe.GetRef());
            return Nothing();
        }

        return result;
    }

    bool TryChangeAlerts(IDeployInfoRequest& request, const TString& service, const TString& ctype, TString& message) {
        try {
            NJson::TJsonValue alertsResult(NJson::JSON_MAP);
            TAlertsMaker maker(service, ctype, request);
            if (!maker.IsValid()) {
                message = "invalid alerts maker: " + maker.GetErrorMessage();
                return false;
            }
            bool alertsSuccess = maker.ActualizeFullService(true, alertsResult);
            if (!alertsSuccess) {
                message = "failed to change alerts: " + alertsResult["error"].GetStringRobust();
                return false;
            }
            message = "changed alerts: " + alertsResult["done_alerts"]["cnt_changed"].GetStringRobust();
            return true;
        } catch (yexception& e) {
            message = TString("exception while alerts update: ") + e.what();
        }
        return true;
    }
}

namespace NRTYDeploy {

    bool TScriptProcessSLADescription::Process(IDeployInfoRequest& request) {
        const TCgiParameters& params = request.GetRD().CgiParam;

        const TString& service = params.Get("service");
        const TString& ctype = params.Get("ctype");
        const TString& action = params.Get("action");

        if (service.empty()) {
            ReportError(request, "service is missing");
            return false;
        }
        if (ctype.empty()) {
            ReportError(request, "ctype is missing");
            return false;
        }

        NSaasProto::TSlaDescription description;
        if (!TryGetSLADescription(service, request.GetStorage(), description)) {
            ReportError(request, "Failed to load sla description");
            return false;
        } else if (params.Has("use_default") && IsTrue(params.Get("use_default"))) {
            if (!TryGetSLADefault(ctype, request.GetStorage(), description)) {
                ReportError(request, "Failed to load sla description default");
                return false;
            }
        }

        const bool isSet = (action == "set");
        const bool isPatch = !isSet && (action == "patch");
        if (isSet || isPatch) {
            const TMaybe<NJson::TJsonValue> resultMaybe = ParseInput(request, !isPatch);
            if (!resultMaybe.Defined()) {
                return false;
            }

            PatchDescription(description, resultMaybe.GetRef(), ctype);

            try {
                const SLAUpdateStatus updateStatus = StoreSLADescription(service, request.GetStorage(), description);
                NJson::TJsonValue reply;
                reply.InsertValue("content_hash", MD5::Calc(updateStatus.NewDataProto));
                if (updateStatus.Version != -1) {
                    reply.InsertValue("version", updateStatus.Version);
                }
                TString alertsInfo;
                bool alertsSuccess = TryChangeAlerts(request, service, ctype, alertsInfo);
                if (alertsSuccess) {
                    reply.InsertValue("alerts_processed", alertsInfo);
                } else {
                    reply.InsertValue("warning", alertsInfo);
                }
                request.Output() << "HTTP/1.1 200 \r\n\r\n"
                    << reply.GetStringRobust();
                SetData(updateStatus.OldDataProto, updateStatus.NewDataProto);
            } catch (yexception& e) {
                ReportError(request, TString("Incorrect resource ") + e.what());
                return false;
            }
            return true;
        } else if (action == "get") {
            NJson::TJsonValue result = SerializeDescription(description, ctype);
            request.Output() << "HTTP/1.1 200 \r\n\r\n"
                << result.GetStringRobust();
            return true;
        }
        ReportError(request, "Unknown action: " + action);
        return false;
    }

    IScript::TFactory::TRegistrator<TScriptProcessSLADescription> TScriptProcessSLADescription::Registrator("process_sla_description");
}
