#include "metrics.h"
#include "common_messages.h"

#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/merger_config.h>
#include <saas/rtyserver/config/realm_config.h>

#include <search/session/comsearch.h>
#include <util/datetime/base.h>

static void ReduceHealthPct(ui32 number, float weight, float *health) {
    float reduceRatio = 0.0;
    if (number == 0) {
        return;
    }
    for (ui32 i = 1; i <= number; ++i) {
        reduceRatio += 1.0 / (2.0 * i);
    }
    *health = *health - *health * reduceRatio * weight;
}

void OutShardInfo(IOutputStream& out, const TMessageGetServerHealth& msg) {
    out << "Shards                       :";
    for (const auto& e: msg.ShardInfo) {
        out << " " << e.first << "/" << e.second;
    }
    out << Endl;
}

void OutComponentDigests(IOutputStream& out, const TMessageGetServerHealth& msg) {
    static const ssize_t FIELD_WIDTH = 29;
    for (const auto& p: msg.ComponentDigests) {
        TString name = p.first + "_md5";
        TString padding(name.size() < FIELD_WIDTH ? FIELD_WIDTH - name.size() : 0, ' ');
        out << name << padding << ": " << p.second << Endl;
    }
}

/// Short status to be displayed in Golem
TString GetBriefServerStatus(const TMessageGetServerHealth& statusMessage) {
    //
    // Possible options:
    //
    // * Starting
    // * Repair_Index
    // * Restoring_Backup
    // * Restoring_Realime ( = Fusion_Banned right after Restoring_Backup )
    // * OK
    // * Fusion_Banned
    // * Stopped
    // * NonRunnable

    if (statusMessage.NonRunnable)
        return "NonRunnable;" + statusMessage.NonRunnableDetails;

    switch(statusMessage.RecoveryStage) {
        case TRTYRecoveryStage::ServerNotExist:
            return "Stopped";
        case TRTYRecoveryStage::Repair:
            return "Repair_Index";
        case TRTYRecoveryStage::Sync:
            return "Restoring_Backup";
        case TRTYRecoveryStage::PostSync:
            return "Restoring_Realtime";
        default:
            ;
    }

    if (!statusMessage.Active)
        return "Starting";

    if (statusMessage.BanTimestamp)
        return "Fusion_Banned";

    if (statusMessage.SearchServerRunning)
        return "OK";

    //default (should not happen)
    return "Stopped";
}

void GetServerStatus(IOutputStream& out, const TRTYServerConfig *config, bool brief) {
    TMessageGetServerHealth statusMessage;
    if (!config) {
        out << "HTTP/1.1 400 NotReady\n\n";
        return;
    }
    statusMessage.ComponentDigests.insert(config->FileDigests.begin(), config->FileDigests.end());

    try {
        SendGlobalMessage(statusMessage);
    }
    catch (yexception& e) {
        ERROR_LOG << "status request can't process: " << e.what() << Endl;
        return;
    }
    catch (...) {
        ERROR_LOG << "status request can't process: Unknown error" << Endl;
        return;
    }
    out << "HTTP/1.1 200 OK\n\n";


    TString briefServerStatus = GetBriefServerStatus(statusMessage);
    if (brief) {
        out << briefServerStatus << Endl;
        return;
    }

    //
    // Calculate server health
    //

    float healthPercent = 100.00;

    // servers get killed by bsconfig often, low number is OK here
    if (statusMessage.CrashCount > 2 &&
        // ignore crashes if last merge was successful
        (statusMessage.LastMergeTime == 0 || statusMessage.MergeFailedCount > 0)){
        ReduceHealthPct(statusMessage.CrashCount - 1, 0.35, &healthPercent);
    }

    // If merger failed - bad sign.
    ReduceHealthPct(statusMessage.MergeFailedCount, 0.6, &healthPercent);

    // Look at number of disk indices...
    int maxDiskIndexConfigured = config->GetMergerConfig().MaxSegments;
    int searchableDiskIndexLimit = config->SearchersCountLimit;
    if (!config->SearchersCountLimit) {
        searchableDiskIndexLimit = maxDiskIndexConfigured + 1;
    }
    searchableDiskIndexLimit += config->GetRealmListConfig().GetNumberDiskRealms() - 1; // excluding the main one
    int searchableIndexNumberOverLimit =  (int)statusMessage.ActiveDiskIndexCount - searchableDiskIndexLimit;
    if (searchableIndexNumberOverLimit > 0) {
        ReduceHealthPct(searchableIndexNumberOverLimit, 0.6, &healthPercent);
    }

    int idleIndexNumberOverLimit =  statusMessage.IdleDiskIndexCount - maxDiskIndexConfigured - 4;
    if (idleIndexNumberOverLimit > 0) {
        ReduceHealthPct(idleIndexNumberOverLimit, 0.4, &healthPercent);
    }

    if (statusMessage.RequestDropRate > 0.03) {
        healthPercent *= (1. - statusMessage.RequestDropRate);
    }

    if (healthPercent < 0) {
        healthPercent = 0;
    }

    //
    // Format the response
    //
    out << "Brief                            : " << briefServerStatus << Endl;
    out << "Must_be_alive                    : " << statusMessage.MustBeAlive << Endl;
    out << "Active                           : " << statusMessage.Active << Endl;
    out << "Non_Runnable                     : " << statusMessage.NonRunnable << Endl;
    out << "Server_health_percentage         : " << (int)(healthPercent) << Endl;
    out << "Consecutive_crashes              : " << statusMessage.CrashCount << Endl;
    out << "Index_Merge_failed_count         : " << statusMessage.MergeFailedCount << Endl;
    out << "Index_Merge_last_time            : " << statusMessage.LastMergeTime << Endl;
    out << "Index_Merge_last_elapsed_time    : " << statusMessage.LastMergeElapsedTimed << Endl;
    out << "Active_disk_index_count          : " << statusMessage.ActiveDiskIndexCount << Endl;
    out << "Idle_disk_index_count            : " << statusMessage.IdleDiskIndexCount << Endl;
    out << "Search_Server_Running            : " << statusMessage.SearchServerRunning << Endl;
    out << "Request_drop_rate                : " << statusMessage.RequestDropRate << Endl;
    auto banTime = statusMessage.BanTimestamp != 0 ? Now().Seconds() - statusMessage.BanTimestamp : 0;
    out << "Search_ban_time                  : " << banTime << Endl;
    auto uptime = statusMessage.SlotTimestamp != 0 ? Now().Seconds() - statusMessage.SlotTimestamp : 0;
    out << "Uptime                           : " << uptime << Endl;

    OutShardInfo(out, statusMessage);
    OutComponentDigests(out, statusMessage);
}
