#include "controller.h"

#include <saas/rtyserver/common/common_messages.h>
#include <saas/rtyserver/common/stream_messages.h>
#include <saas/rtyserver/common/message_get_doc_info.h>
#include <saas/rtyserver/common/search_control.h>
#include <saas/rtyserver/indexer_core/reopen.h>
#include <saas/rtyserver/synchronizer/synchronizer.h>

#include <library/cpp/json/json_reader.h>
#include <library/cpp/yconf/patcher/unstrict_config.h>

#include <util/generic/cast.h>
#include <util/generic/map.h>
#include <util/generic/string.h>

namespace {
    void DoSynchronizerCall(const TCgiParameters& cgi, TStringBuf postBuffer, TAsyncTaskExecutor::TTask& task) {
        NRTYServer::TMessageSynchronizerCall message(cgi, postBuffer, task);
        message.Correctness = false;
        SendGlobalMessage(message);
        if (!message.Correctness) {
            ERROR_LOG << "Incorrect synchronizer command processing" << Endl;
            ythrow yexception() << (message.GetInfo().GetType() != NJson::JSON_UNDEFINED ? message.GetInfo().GetStringRobust() : TString("there is no Synchronizer module"));
        }
    }
}

#define DEFINE_COMMAND(name, description) DEFINE_CONTROLLER_COMMAND(TRTYServer, name, description)
#define END_COMMAND END_CONTROLLER_COMMAND

DEFINE_COMMAND(enable_search, "")
    // remove persistent search ban if any
    auto cgi = GetCgi();
    cgi.InsertUnescaped("action", "slot_info");
    cgi.InsertUnescaped("subaction", "remove_persistent_search_ban");
    DoSynchronizerCall(cgi, GetPostBuffer(), *this);

    Write("running", NRTYServer::EnableSearch());
END_COMMAND

DEFINE_COMMAND(disable_search, "")
    Write("running", NRTYServer::DisableSearch());
END_COMMAND

DEFINE_COMMAND(ban_search, "")
    Write("running", NRTYServer::BanSearch());
END_COMMAND

DEFINE_COMMAND(check_search, "")
    Write("running", NRTYServer::CheckSearch());
END_COMMAND

DEFINE_COMMAND(enable_rtsearch, "")
    const auto& result = SendGlobalMessage<TMessageMemorySearchControl>(TMessageMemorySearchControl::mscEnable);
    Write("enabled", result.IsEnabled());
END_COMMAND

DEFINE_COMMAND(disable_rtsearch, "")
    const auto& result = SendGlobalMessage<TMessageMemorySearchControl>(TMessageMemorySearchControl::mscDisable);
    Write("enabled", result.IsEnabled());
END_COMMAND

DEFINE_COMMAND(check_rtsearch, "")
    const auto& result = SendGlobalMessage<TMessageMemorySearchControl>();
    Write("enabled", result.IsEnabled());
END_COMMAND

DEFINE_COMMAND(enable_indexing, "")
    const auto& result = SendGlobalMessage<TMessageIndexingControl>(TMessageIndexingControl::Enable);
    Write("running", result.IsRunning());
END_COMMAND

DEFINE_COMMAND(disable_indexing, "")
    const auto& result = SendGlobalMessage<TMessageIndexingControl>(TMessageIndexingControl::Disable);
    Write("running", result.IsRunning());
END_COMMAND

DEFINE_COMMAND(check_indexing, "")
    const auto& result = SendGlobalMessage<TMessageIndexingControl>(TMessageIndexingControl::GetStatus);
    Write("running", result.IsRunning());
END_COMMAND

DEFINE_COMMAND(wait_empty_indexing_queues, "")
    Owner->GetServer()->GetLogicServer().GetMeAs<TRTYServer>().WaitEmptyQueues();
END_COMMAND

DEFINE_COMMAND(reduce_control, "arguments: action")
    auto config = Owner->GetConfig();
    IExternalServiceLogic::TPtr logic = config ? config->GetMeAs<TRTYServerConfig>().ExternalServiceLogic : nullptr;
    if (!!logic) {
        Write("result", logic->ExternalControl(GetCgi().Get("action")));
    } else {
        Write("result", "no external logic");
    }
END_COMMAND

DEFINE_COMMAND(is_final_index, "arguments: directory")
    const TString dir = GetCgi().Get("directory");
    Write("directory", dir);
    Write("result", Owner->GetServer()->GetLogicServer().GetMeAs<TRTYServer>().GetIndexStorage().IsFinalIndex(dir));
END_COMMAND

DEFINE_COMMAND(synchronizer, "")
    DoSynchronizerCall(GetCgi(), GetPostBuffer(), *this);
END_COMMAND

DEFINE_COMMAND(is_repairing, "")
    Write("result", Owner->GetServer()->GetLogicServer().GetMeAs<TRTYServer>().IsRepairing());
END_COMMAND

DEFINE_COMMAND(get_final_indexes, "arguments: full_path=boolean")
    bool fullPath = FromString<bool>(GetCgi().Get("full_path"));
    if (Owner && Owner->GetServer()) {
        TVector<TString> indexDirs = SplitString(Owner->GetServer()->GetLogicServer().GetMeAs<TRTYServer>().GetIndexStorage().GetFinalIndexDirs(fullPath), " ");
        NJson::TJsonValue dirs(NJson::JSON_ARRAY);
        for (TVector<TString>::const_iterator dir = indexDirs.begin(); dir != indexDirs.end(); ++dir)
            dirs.AppendValue(*dir);
        Write("dirs", dirs);
    } else {
        Write("error", "Non-initialized server");
    }
END_COMMAND

DEFINE_COMMAND(get_cache_policy, "")
    auto cfg = Owner->GetConfig();
    if (!cfg)
        ythrow yexception() << "rtyserver not ready";
    Write("result", ToString(cfg->GetMeAs<TRTYServerConfig>().GetCachePolicy()));
END_COMMAND

DEFINE_COMMAND(create_merger_tasks, "")
    SendGlobalMessage<TMessageCreateMergerTasks>();
END_COMMAND

DEFINE_COMMAND(do_all_merger_tasks, "arguments: wait=boolean (default: true)")
    SendGlobalMessage<TMessageCreateMergerTasks>();
    bool wait = GetCgi().Has("wait") ? FromString<bool>(GetCgi().Get("wait")) : true;
    if (wait) {
        const TRTYServer& server = Owner->GetServer()->GetLogicServer().GetMeAs<TRTYServer>();
        server.GetMerger().WaitMergingFinished(server.Config.GetRealmListConfig().GetMainRealmConfig().RealmDirectory);
    }
END_COMMAND

DEFINE_COMMAND(save_config, "")
    try {
        ((TRTYController*)Owner)->SaveConfig();
        Write("result", true);
    } catch (...) {
        Write("result", false);
        Write("error", CurrentExceptionMessage());
    }
END_COMMAND

DEFINE_COMMAND(get_docfetcher_status, "")
    const auto& status = SendGlobalMessage<TMessageGetDocfetcherStatus>();
    if (status.DocfetcherPresent) {
        Write("queue_size", status.QueueSize);
        Write("docs_indexing", status.DocsIndexing);
        Write("has_docs_in_distributor", status.HasDocsInDistributor);
        Write("timestamp", status.Timestamp);
        Write("status", status.Status);
        Write("replies", status.Replies);
    }
END_COMMAND


DEFINE_COMMAND(set_slot_info, "alias for 'command=synchronizer&action=slot_info&subaction=set'")
    auto cgi = GetCgi();
    cgi.InsertUnescaped("action", "slot_info");
    cgi.InsertUnescaped("subaction", "set");
    DoSynchronizerCall(cgi, GetPostBuffer(), *this);
END_COMMAND

DEFINE_COMMAND(remove_persistent_search_ban, "alias for 'command=synchronizer&action=slot_info&subaction=remove_persistent_search_ban'")
    auto cgi = GetCgi();
    cgi.InsertUnescaped("action", "slot_info");
    cgi.InsertUnescaped("subaction", "remove_persistent_search_ban");
    DoSynchronizerCall(cgi, GetPostBuffer(), *this);
END_COMMAND

DEFINE_COMMAND(get_doc_info, "arguments: docid=string")
    const TString& docId = GetCgi().Get("docid");

    const auto& docInfo = SendGlobalMessage<TMessageGetDocInfo>(TDocHandle(docId));
    Write("docid", docId);
    if (!docInfo.HasReply) {
        Write("error", docInfo.ErrorMessage);
        return;
    }
    Write("info", docInfo.Reply);
END_COMMAND

DEFINE_COMMAND(clear_index, "destroying server, removing IndexDir")
    Owner->DestroyServer(Max<ui32>());
    auto cfg = Owner->GetConfig();
    if (!cfg)
        ythrow yexception() << "rtyserver not ready";
    for (auto&& [realmName, realmConfig] : cfg->GetMeAs<TRTYServerConfig>().GetRealmListConfig().RealmsConfig) {
        if (realmConfig.RealmDirectory) {
            TFsPath path(realmConfig.RealmDirectory + "/");
            if (path.Exists()) {
                path.ForceDelete();
                path.MkDirs();
            }
        }
    }
    Owner->ClearServerDataStatus();
END_COMMAND

DEFINE_COMMAND(pause_docfetcher, "")
    const auto& message = SendGlobalMessage<TMessagePauseDocfetcher>();
    Write("result", message.GetMessageIsProcessed());
END_COMMAND

DEFINE_COMMAND(continue_docfetcher, "")
    const auto& message = SendGlobalMessage<TMessageContinueDocfetcher>();
    Write("result", message.GetMessageIsProcessed());
END_COMMAND

DEFINE_COMMAND(set_docfetcher_timestamp, "arguments: increment_only=boolean, timestamp=timestamp or age=ui32 (seconds)")
    TMessageSetDocfetcherTimestamp message;
    const TCgiParameters& cgi = GetCgi();
    if (cgi.Has("timestamp")) {
        message.Timestamp = FromString<NRTYServer::TTimestampValue>(cgi.Get("timestamp"));
    } else if (cgi.Has("age")) {
        message.Timestamp = Seconds() - FromString<ui32>(cgi.Get("age"));
    } else {
        throw yexception() << "either 'timestamp' or 'age' should be specified";
    }
    if (cgi.Has("increment_only"))
        message.IncrementOnly = IsTrue(cgi.Get("increment_only"));

    SendGlobalMessage(message);
    Write("result", message.GetMessageIsProcessed());
END_COMMAND

DEFINE_COMMAND(set_docfetcher_datacenters_status, "arguments: block_time=time, datacenters=dc1[,dc2]* failed=boolean")
    TMessageSetDocfetcherDatacentersStatus message;
    const TCgiParameters& cgi = GetCgi();
    if (cgi.Has("block_time")) {
        if (!TDuration::TryParse(cgi.Get("block_time"), message.BlockTime)) {
            throw yexception() << "Failed to parse 'block_time'";
        }
    } else {
        throw yexception() << "'block_time' should be specified";
    }
    if (cgi.Has("datacenters")) {
        message.Datacenters = SplitString(cgi.Get("datacenters"), ",");
    } else {
        throw yexception() << "'datacenters' should be specified";
    }
    if (cgi.Has("failed")) {
        message.Failed = FromString<bool>(cgi.Get("failed"));
    } else {
        throw yexception() << "'failed' should be specified";
    }
    SendGlobalMessage(message);
    Write("result", message.GetMessageIsProcessed());
END_COMMAND

DEFINE_COMMAND(set_its_override, "arguments: key, value or revert")
    TMessageItsOverride message;
    const TCgiParameters& cgi = GetCgi();
    if (cgi.Has("key")) {
        message.Key = cgi.Get("key");
    } else {
        throw yexception() << "'key' should be specified";
    }

    if (cgi.Has("value")) {
        message.Value = cgi.Get("value");
    } else if (IsTrue(cgi.Get("revert"))) {
        message.Value = TMaybe<TString>();
    } else {
        throw yexception() << "either 'value' or 'revert' should be specified";
    }

    SendGlobalMessage(message);
    Write("result", message.GetMessageIsProcessed());
END_COMMAND

DEFINE_COMMAND(reopen_indexes, "arguments: wait=boolean (default: false), timeout=ui32 (milliseconds, 0 means infinite timeout, default: 0)")
    const bool wait = IsTrue(GetCgi().Get("wait"));
    if (Y_UNLIKELY(wait)) {
        if (auto cfg = Owner->GetConfig()) {
            const auto timeout = GetCgi().Has("timeout") ? FromString<ui32>(GetCgi().Get("timeout")) : 0;
            const auto& rtyServerConfig = cfg->GetMeAs<TRTYServerConfig>();
            Write("result", NRTYServer::ReopenIndexersAndWait(rtyServerConfig, timeout));
            return;
        }
    }
    Write("result", NRTYServer::ReopenIndexers());
END_COMMAND

DEFINE_COMMAND(get_timestamp, "arguments: stream=streamid")
    TMessageGetIndexTimestamp message(IIndexController::DISK | IIndexController::FINAL | IIndexController::PREPARED | IIndexController::MEMORY);
    SendGlobalMessage(message);

    ui64 timestamp = 0;
    ui64 minimum = 0;
    ui64 average = 0;
    ui64 updateTimestamp = 0;
    const TString& streamCgi = GetCgi().Get("stream");
    if (streamCgi) {
        NRTYServer::TStreamId stream = FromString<NRTYServer::TStreamId>(streamCgi);
        timestamp = SafeIntegerCast<ui64>(message.GetTimestamp().Get(stream));
        minimum = SafeIntegerCast<ui64>(message.GetTimestamp().GetMin(stream));
        average = message.GetTimestamp().GetAvg(stream);
        updateTimestamp = message.GetTimestamp().GetUpdateTimestamp(stream);
        Write("stream", stream);
    } else {
        timestamp = SafeIntegerCast<ui64>(message.GetTimestamp().Get());
        minimum = SafeIntegerCast<ui64>(message.GetTimestamp().GetMin());
        average = message.GetTimestamp().GetAvg();
        updateTimestamp = message.GetTimestamp().GetUpdateTimestamp();
    }
    const i64  age = timestamp ? Seconds() - timestamp : 0;
    const i64  ageAvg = average ? Seconds() - average : 0;
    Write("timestamp", timestamp);
    Write("min_timestamp", minimum);
    Write("update_timestamp", updateTimestamp);
    Write("average", ageAvg);
    Write("age", age);
END_COMMAND

DEFINE_COMMAND(get_searchable_timestamp, "arguments: stream=streamid")
    TMessageGetSearchableTimestamp message;
    SendGlobalMessage(message);

    ui64 timestamp = 0;
    const TString& streamCgi = GetCgi().Get("stream");
    if (streamCgi) {
        timestamp = SafeIntegerCast<ui64>(message.GetTimestamp().Get(FromString<NRTYServer::TStreamId>(streamCgi)));
    } else {
        timestamp = SafeIntegerCast<ui64>(message.GetTimestamp().Get());
    }
    Write("timestamp", timestamp);
END_COMMAND

DEFINE_COMMAND(get_state, "")
    const auto& message = SendGlobalMessage<TMessageGetState>();
    Write("state", message.State);
END_COMMAND

DEFINE_COMMAND(get_histogram, "")
    const auto& message = SendGlobalMessage<TMessageGetIndexHistogram>();
    Write("histogram", message.Histograms.Serialize<NJson::TJsonValue>());
END_COMMAND

DEFINE_COMMAND(set_timestamp, "arguments: timestamp=timestamp, stream=streamid")
    const TCgiParameters& cgi = GetCgi();

    TMessageSetTimestamp message;
    message.Timestamp = FromString<NRTYServer::TTimestampValue>(cgi.Get("timestamp"));
    message.Stream = FromString<NRTYServer::TStreamId>(cgi.Get("stream"));

    SendGlobalMessage(message);
    Write("result", message.GetMessageIsProcessed());
END_COMMAND

DEFINE_COMMAND(get_used_factors, "")
    const auto& factors = SendGlobalMessage<TMessageGetUsedFactors>();
    Write("factors_info", factors.GetFactorsConfig());
    Write("used_factors", factors.GetFactorsUsage());
END_COMMAND

#undef DEFINE_COMMAND
#undef END_COMMAND
