#include "action.h"

#include <library/cpp/json/json_writer.h>

namespace {
    inline void SetToJson(const TSet<TString>& values, NJson::TJsonValue& parentElement) {
        for (const auto& value: values) {
            parentElement.AppendValue(value);
        }
    }

    template<typename T>
    inline void MaybeToJson(const TString& name, const TMaybe<T>& mayBe, NJson::TJsonValue& element) {
        if (mayBe.Defined())
            element[name] = mayBe.GetRef();
    }

    template<typename T>
    inline T JsonAs(const NJson::TJsonValue& element);

    template<>
    inline ui64 JsonAs(const NJson::TJsonValue& element) {
        return element.GetUInteger();
    }

    template<>
    inline double JsonAs(const NJson::TJsonValue& element) {
        return element.GetDouble();
    }

    template<typename T>
    inline void JsonToMaybe(const TString& name, const NJson::TJsonValue& element, TMaybe<T>& mayBe) {
        if (element.Has(name))
            mayBe = JsonAs<T>(element[name]);
        else
            mayBe = Nothing();
    }
}

namespace NDaemonController {

    NJson::TJsonValue TProcessSLADescriptionAction::DoSerializeToJson() const {
        NJson::TJsonValue res;
        SetToJson(Owners, res["owners"]);
        SetToJson(Responsibles, res["responsibles"]);
        res["service"] = Service;
        res["ctype"] = CType;
        if (Ticket.Defined())
            res["ticket"] = Ticket.GetRef();
        res["is_patch"] = IsPatch;
        res["use_default"] = UseDefault;

        if (Version >= 0) {
            res["version"] = Version;
        }
        if (MD5Hash) {
            res["md5"] = MD5Hash;
        }
        MaybeToJson("maxdocs", MaxDocsMaybe, res);
        MaybeToJson("search_rps", SearchRPSMaybe, res);
        MaybeToJson("search_rps_planned", SearchRPSPlannedMaybe, res);
        MaybeToJson("index_rps", IndexRPSMaybe, res);
        MaybeToJson("total_index_size_bytes", TotalIndexSizeBytesMaybe, res);
        MaybeToJson("search_q_99_ms", SearchQ99MsMaybe, res);
        MaybeToJson("search_q_999_ms", SearchQ999MsMaybe, res);
        MaybeToJson("unanswers_5min_perc_warn", Unanswers5MinPercWarnMaybe, res);
        MaybeToJson("unanswers_5min_perc_crit", Unanswers5MinPercCritMaybe, res);
        MaybeToJson("unanswers_5min_perc_crit", Unanswers5MinPercCritMaybe, res);
        MaybeToJson("service_weight", ServiceWeightMaybe, res);
        MaybeToJson("abc_user_service", ABCUserServiceMaybe, res);
        MaybeToJson("abc_quota_service", ABCQuotaServiceMaybe, res);
        MaybeToJson("disaster_alerts", DisasterAlertsMaybe, res);
        SetToJson(Ferrymans, res["ferrymans"]);

        return res;
    }

    void TProcessSLADescriptionAction::DoDeserializeFromJson(const NJson::TJsonValue& json) {
        Service = json["service"].GetString();
        CType = json["ctype"].GetString();
        IsPatch = json.Has("is_patch") && json["is_patch"].GetBoolean();
        UseDefault = json.Has("use_default") && json["use_default"].GetBoolean();

        Owners.clear();
        for(const auto& jsonOwner: json["owners"].GetArray()) {
            Owners.insert(jsonOwner.GetString());
        }
        Responsibles.clear();
        for(const auto& jsonResp: json["owners"].GetArray()) {
            Responsibles.insert(jsonResp.GetString());
        }
        if (json.Has("ticket"))
            Ticket = json["ticket"].GetString();

        JsonToMaybe("maxdocs", json, MaxDocsMaybe);
        JsonToMaybe("search_rps", json, SearchRPSMaybe);
        JsonToMaybe("search_rps_planned", json, SearchRPSPlannedMaybe);
        JsonToMaybe("index_rps", json, IndexRPSMaybe);
        JsonToMaybe("total_index_size_bytes", json, TotalIndexSizeBytesMaybe);
        JsonToMaybe("search_q99_ms", json, SearchQ99MsMaybe);
        JsonToMaybe("search_q999_ms", json, SearchQ999MsMaybe);
        JsonToMaybe("unanswers_5min_perc_warn", json, Unanswers5MinPercWarnMaybe);
        JsonToMaybe("unanswers_5min_perc_crit", json, Unanswers5MinPercCritMaybe);
        JsonToMaybe("service_weight", json, ServiceWeightMaybe);
        JsonToMaybe("abc_user_service", json, ABCUserServiceMaybe);
        JsonToMaybe("abc_quota_service", json, ABCQuotaServiceMaybe);
        JsonToMaybe("disaster_alerts", json, DisasterAlertsMaybe);

        Ferrymans.clear();
        for(const auto& ferryman: json["ferrymans"].GetArray()) {
            Ferrymans.insert(ferryman.GetString());
        }

        if (json.Has("version"))
            Version = json["version"].GetInteger();
        else
            Version = -1;

        if (json.Has("md5"))
            MD5Hash = json["md5"].GetString();
        else
            MD5Hash.clear();
    }

    void TProcessSLADescriptionAction::DoInterpretResult(const TString& result) {
        NJson::TJsonValue json;
        TStringInput si(result);
        if (!NJson::ReadJsonTree(&si, &json) || !json.Has("content_hash"))
            Fail(result);
        else {
            MD5Hash = json["content_hash"].GetString();
            if (json.Has("version")) {
                Version = json["version"].GetIntegerRobust();
                Success("");
            } else
                Success("Unchanged");
        }
    }

    TProcessSLADescriptionAction::TProcessSLADescriptionAction(const TString& service, const TString& ctype)
        : Service(service)
        , CType(ctype)
    {}

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetOwners(const TSet<TString>& owners) {
        Owners = owners;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetResponsibles(const TSet<TString>& responsibles) {
        Responsibles = responsibles;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetTicket(const TString& ticket) {
        Ticket = ticket;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetMaxDocsPerService(ui64 maxDocs) {
        MaxDocsMaybe = maxDocs;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetSearchRPS(ui64 searchRPS) {
        SearchRPSMaybe = searchRPS;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetSearchRPSPlanned(ui64 searchRPSPlanned) {
        SearchRPSPlannedMaybe = searchRPSPlanned;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetIndexRPS(ui64 indexRPS) {
        IndexRPSMaybe = indexRPS;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetTotalIndexSizeBytes(ui64 totalIndexSizeBytes) {
        TotalIndexSizeBytesMaybe = totalIndexSizeBytes;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetSearchQ99Ms(ui64 searchQ99Ms) {
        SearchQ99MsMaybe = searchQ99Ms;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetSearchQ999Ms(ui64 searchQ999Ms) {
        SearchQ999MsMaybe = searchQ999Ms;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetUnanswers5MinPercWarn(double unanswers5MinPercWarn) {
        Unanswers5MinPercWarnMaybe = unanswers5MinPercWarn;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetUnanswers5MinPercCrit(double unanswers5MinPercCrit) {
        Unanswers5MinPercCritMaybe = unanswers5MinPercCrit;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetServiceWeight(const ui64 serviceWeight) {
        ServiceWeightMaybe = serviceWeight;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetABCUserService(ui64 abcUserService) {
        ABCUserServiceMaybe = abcUserService;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetABCQuotaService(ui64 abcQuotaService) {
        ABCQuotaServiceMaybe = abcQuotaService;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetDisasterAlerts(ui64 disasterAlerts) {
        DisasterAlertsMaybe = disasterAlerts;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetFerrymans(const TSet<TString>& ferrymans) {
        Ferrymans = ferrymans;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetPatch() {
        IsPatch = true;
        return *this;
    }

    TProcessSLADescriptionAction& TProcessSLADescriptionAction::SetUseDefault() {
        UseDefault = true;
        return *this;
    }

    TDuration TProcessSLADescriptionAction::GetTimeoutDuration() const {
        return TDuration::Seconds(10);
    }

    TString TProcessSLADescriptionAction::GetCustomUri() const {
        return "process_sla_description";
    }

    TString TProcessSLADescriptionAction::DoBuildCommand() const {
        return TString::Join("service=", Service, "&ctype=", CType, "&action=", (IsPatch ? "patch" : "set"), "&use_default=", ToString(UseDefault));
    }

    bool TProcessSLADescriptionAction::GetPostContent(TString& result) const {
        NJson::TJsonValue value(NJson::JSON_MAP);

        if (!IsPatch || !Owners.empty())
            SetToJson(Owners, value["owners"]);

        if (!IsPatch || !Responsibles.empty())
            SetToJson(Responsibles, value["responsibles"]);

        if (!IsPatch || Ticket.Defined())
            MaybeToJson("ticket", Ticket, value);

        MaybeToJson("maxdocs", MaxDocsMaybe, value);
        MaybeToJson("search_rps", SearchRPSMaybe, value);
        MaybeToJson("search_rps_planned", SearchRPSPlannedMaybe, value);
        MaybeToJson("index_rps", IndexRPSMaybe, value);
        MaybeToJson("total_index_size_bytes", TotalIndexSizeBytesMaybe, value);
        MaybeToJson("search_q_99_ms", SearchQ99MsMaybe, value);
        MaybeToJson("search_q_999_ms", SearchQ999MsMaybe, value);
        MaybeToJson("unanswers_5min_perc_warn", Unanswers5MinPercWarnMaybe, value);
        MaybeToJson("unanswers_5min_perc_crit", Unanswers5MinPercCritMaybe, value);
        MaybeToJson("service_weight", ServiceWeightMaybe, value);
        MaybeToJson("abc_user_service", ABCUserServiceMaybe, value);
        MaybeToJson("abc_quota_service", ABCQuotaServiceMaybe, value);
        MaybeToJson("disaster_alerts", DisasterAlertsMaybe, value);
        SetToJson(Ferrymans, value["ferrymans"]);

        result = NJson::WriteJson(value);
        return true;
    }

    TString TProcessSLADescriptionAction::ActionName() const {
        return PROCESS_SLA_DESCRIPTION_ACTION_NAME;
    }

    TProcessSLADescriptionAction::TFactory::TRegistrator<TProcessSLADescriptionAction> TProcessSLADescriptionAction::Registrator(PROCESS_SLA_DESCRIPTION_ACTION_NAME);

}
