// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)

#include "cli.h"
#include "cluster_client.h"
#include "str_util.h"
#include "json_util.h"
#include "http_client.h"

// Reweight OSD, change tags or set noout flag
struct osd_changer_t
{
    cli_tool_t *parent;

    uint64_t osd_num = 0;
    bool set_tags = false;
    std::vector<std::string> new_tags;
    bool set_reweight = false;
    double new_reweight = 1;
    bool set_noout = false;
    double new_noout = false;
    bool force = false;

    json11::Json::object osd_cfg;
    uint64_t osd_cfg_mod_rev = 0;
    json11::Json::array compare, success;

    int state = 0;
    std::function<bool(cli_result_t &)> cb;
    cli_result_t result;

    bool is_done()
    {
        return state == 100;
    }

    void loop()
    {
        if (state == 1)
            goto resume_1;
        else if (state == 2)
            goto resume_2;
        if (!osd_num)
        {
            result = (cli_result_t){ .err = EINVAL, .text = "OSD number is missing" };
            state = 100;
            return;
        }
        if (!set_tags && !set_reweight && !set_noout)
        {
            result = (cli_result_t){ .err = EINVAL, .text = "Nothing to update" };
            state = 100;
            return;
        }
        if (set_reweight && new_reweight < 0)
        {
            result = (cli_result_t){ .err = EINVAL, .text = "Reweight can't be negative" };
            state = 100;
            return;
        }
        if (set_reweight && new_reweight > 1)
        {
            result = (cli_result_t){ .err = EINVAL, .text = "Reweight can't be larger than 1" };
            state = 100;
            return;
        }
        parent->etcd_txn(json11::Json::object {
            { "success", json11::Json::array {
                json11::Json::object {
                    { "request_range", json11::Json::object {
                        { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/osd/stats/"+std::to_string(osd_num)) },
                    } },
                },
                json11::Json::object {
                    { "request_range", json11::Json::object {
                        { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd/"+std::to_string(osd_num)) },
                    } },
                },
            } },
        });
        state = 1;
resume_1:
        if (parent->waiting > 0)
            return;
        if (parent->etcd_err.err)
        {
            result = parent->etcd_err;
            state = 100;
            return;
        }
        {
            auto osd_stats = parent->cli->st_cli.parse_etcd_kv(parent->etcd_result["responses"][0]["response_range"]["kvs"][0]).value;
            if (!osd_stats.is_object() && !force)
            {
                result = (cli_result_t){ .err = ENOENT, .text = "OSD "+std::to_string(osd_num)+" does not exist. Use --force to set configuration anyway" };
                state = 100;
                return;
            }
            auto kv = parent->cli->st_cli.parse_etcd_kv(parent->etcd_result["responses"][1]["response_range"]["kvs"][0]);
            osd_cfg_mod_rev = kv.mod_revision;
            osd_cfg = kv.value.object_items();
            if (set_reweight)
            {
                if (new_reweight != 1)
                    osd_cfg["reweight"] = new_reweight;
                else
                    osd_cfg.erase("reweight");
            }
            if (set_tags)
            {
                if (new_tags.size())
                    osd_cfg["tags"] = new_tags;
                else
                    osd_cfg.erase("tags");
            }
            if (set_noout)
            {
                if (new_noout)
                    osd_cfg["noout"] = true;
                else
                    osd_cfg.erase("noout");
            }
            if (osd_cfg_mod_rev)
            {
                compare.push_back(json11::Json::object {
                    { "target", "MOD" },
                    { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd/"+std::to_string(osd_num)) },
                    { "result", "LESS" },
                    { "mod_revision", osd_cfg_mod_rev+1 },
                });
            }
            else
            {
                compare.push_back(json11::Json::object {
                    { "target", "VERSION" },
                    { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd/"+std::to_string(osd_num)) },
                    { "version", 0 },
                });
            }
            if (!osd_cfg.size())
            {
                success.push_back(json11::Json::object {
                    { "request_delete_range", json11::Json::object {
                        { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd/"+std::to_string(osd_num)) },
                    } },
                });
            }
            else
            {
                success.push_back(json11::Json::object {
                    { "request_put", json11::Json::object {
                        { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd/"+std::to_string(osd_num)) },
                        { "value", base64_encode(json11::Json(osd_cfg).dump()) },
                    } },
                });
            }
        }
        parent->etcd_txn(json11::Json::object {
            { "compare", compare },
            { "success", success },
        });
        state = 2;
resume_2:
        if (parent->waiting > 0)
            return;
        if (parent->etcd_err.err)
        {
            result = parent->etcd_err;
            state = 100;
            return;
        }
        if (!parent->etcd_result["succeeded"].bool_value())
        {
            result = (cli_result_t){ .err = EAGAIN, .text = "OSD "+std::to_string(osd_num)+" configuration was modified by someone else, please repeat your request" };
            state = 100;
            return;
        }
        result = (cli_result_t){
            .err = 0,
            .text = "OSD "+std::to_string(osd_num)+" configuration modified",
            .data = osd_cfg,
        };
        state = 100;
    }
};

std::function<bool(cli_result_t &)> cli_tool_t::start_modify_osd(json11::Json cfg)
{
    auto changer = new osd_changer_t();
    changer->parent = this;
    changer->osd_num = cfg["osd_num"].uint64_value();
    if (!cfg["tags"].is_null())
    {
        changer->set_tags = true;
        if (cfg["tags"].is_string())
        {
            if (cfg["tags"].string_value() != "")
                changer->new_tags = explode(",", cfg["tags"].string_value(), true);
        }
        else if (cfg["tags"].is_array())
        {
            for (auto item: cfg["tags"].array_items())
                changer->new_tags.push_back(item.as_string());
        }
    }
    if (!cfg["reweight"].is_null())
    {
        changer->set_reweight = true;
        changer->new_reweight = cfg["reweight"].number_value();
    }
    if (!cfg["noout"].is_null())
    {
        changer->set_noout = true;
        changer->new_noout = json_is_true(cfg["noout"]);
    }
    changer->force = cfg["force"].bool_value();
    return [changer](cli_result_t & result)
    {
        changer->loop();
        if (changer->is_done())
        {
            result = changer->result;
            delete changer;
            return true;
        }
        return false;
    };
}
