#include "controllers_checker.h"
#include <saas/library/daemon_base/actions_engine/controller_client.h>
#include <saas/library/daemon_base/protos/status.pb.h>
#include <saas/util/external/dc.h>

namespace {

    class TServerStatusGuardWriter {
    public:
        TServerStatusGuardWriter(NRTYDeploy::IVersionedStorage& storage,
                                 NRTYDeploy::TLocalSlotCheckerStorage& localStorage,
                                 const TDeployManagerConfig::TCheckerConfig& config,
                                 const TString& slotName)
            : Storage(storage)
            , LocalStorage(localStorage)
            , Config(config)
            , SlotName(slotName)
            , Updated(false)
            , Loaded(true)
            , NormalSlot(true)
        {
            NController::TSlotChecker statusInfo;
            if (Config.Persist) {
                try {
                    const TString key(TString::Join("/slots_info/", slotName, "/server_status"));
                    Storage.GetValue(key, statusInfo, -1, false);
                } catch (...) {
                    Loaded = false;
                }
            } else {
                Loaded = LocalStorage.GetValue(slotName, statusInfo);
            }

            if (Loaded) {
                Info = NRTYDeploy::IControllersChecker::TControllerStatus(statusInfo);
                if (Info.LastPing + TDuration::Seconds(Config.CacheValidityPeriodSeconds) >= Now()) {
                    Obsolete = false;
                }

                if (Info.Status == NController::TSlotStatus::FailedIndex || Info.Status == NController::TSlotStatus::UnknownError) {
                    INFO_LOG << "Slot " << SlotName << " detected as "
                             << NController::TSlotStatus_Name(Info.Status) << Endl;
                    NormalSlot = false;
                } else {
                    ui32 time = Now().Hours();
                    ui32 countChecks = 0;
                    ui32 fails = 0;
                    ui32 success = 0;
                    for (ui32 i = 0; i < Config.HoursForCheck; ++i) {
                        if (Info.PingStatusesByTime.contains(time - i)) {
                            ++countChecks;
                            fails += Info.PingStatusesByTime[time - i].Fails;
                            success += Info.PingStatusesByTime[time - i].Success;
                        }
                    }

                    if (Config.HoursForCheck - countChecks <= 1) {
                        NormalSlot = (100.0 * fails / (fails + success)) < Config.PercentForCheck;
                    }
                }
            }
        }

        ~TServerStatusGuardWriter() {
            if (Updated) {
                NController::TSlotChecker slotChecker = Info.SerializeToProto();
                if (Config.Persist) {
                    const TString key(TString::Join("/slots_info/", SlotName, "/server_status"));
                    Storage.SetValue(key, slotChecker, false);
                } else {
                    LocalStorage.SetValue(SlotName, slotChecker);
                }
            }
        }

        NRTYDeploy::TControllersChecker::TControllerStatus& GetInfo() {
            Updated = true;
            return Info;
        }

        const NRTYDeploy::TControllersChecker::TControllerStatus& ReadInfo() const {
            return Info;
        }

        bool IsNormalSlot() const {
            return NormalSlot;
        }

        bool NoStorageErrors() const {
            return Loaded;
        }

        bool IsObsolete() const noexcept {
            return Obsolete;
        }


    private:
        NRTYDeploy::IVersionedStorage& Storage;
        NRTYDeploy::TLocalSlotCheckerStorage& LocalStorage;
        const TDeployManagerConfig::TCheckerConfig& Config;
        const TString SlotName;
        bool Updated;
        bool Loaded;
        bool NormalSlot;
        bool Obsolete = true;
        NRTYDeploy::IControllersChecker::TControllerStatus Info;
    };
}


namespace NRTYDeploy {
    TControllersChecker::TControllersChecker(IVersionedStorage& storage, const TDeployManagerConfig::TCheckerConfig& config)
        : IControllersChecker(config)
        , Storage(storage)
        , LocalStorage(TDuration::Hours(72)) // History depth for info
    {}

    IControllersChecker::TSlotInfo TControllersChecker::UpdateSlot(const NRTYCluster::TSlotData& sd, const NJson::TJsonValue& serverStatusGlobal) const {
        TServerStatusGuardWriter g(Storage, LocalStorage, Config, sd.ShortSlotName());
        INFO_LOG << "update slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << Endl;
        g.GetInfo().LastPing = Now();
        g.GetInfo().ReadControllerReply(serverStatusGlobal);
        g.GetInfo().AddInfoByTime();
        if (IS_LOG_ACTIVE(TLOG_DEBUG)) {
            DEBUG_LOG << "update slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << ": " << g.GetInfo().SerializeToJson().GetStringRobust() << Endl;
        } else {
            INFO_LOG << "updated slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << Endl;
        }
        return TSlotInfo(g.GetInfo(), g.IsNormalSlot(), g.NoStorageErrors());
    }

    IControllersChecker::TSlotInfo TControllersChecker::FailSlot(const NRTYCluster::TSlotData& sd) const {
        TServerStatusGuardWriter g(Storage, LocalStorage, Config, sd.ShortSlotName());
        INFO_LOG << "fail slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << Endl;
        g.GetInfo().LastPing = Now();
        g.GetInfo().Status = NController::TSlotStatus::Unavailable;
        g.GetInfo().AddInfoByTime();
        if (IS_LOG_ACTIVE(TLOG_DEBUG)) {
            DEBUG_LOG << "update slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << ": " << g.GetInfo().SerializeToJson().GetStringRobust() << Endl;
        } else {
            INFO_LOG << "updated slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << Endl;
        }
        return TSlotInfo(g.GetInfo(), g.IsNormalSlot(), g.NoStorageErrors());
    }

    IControllersChecker::TSlotInfo TControllersChecker::CheckSlot(const NRTYCluster::TSlotData& sd) const {
        TServerStatusGuardWriter g(Storage, LocalStorage, Config, sd.ShortSlotName());
        if (!Config.Enabled || !g.IsObsolete()) {
            return TSlotInfo(g.ReadInfo(), g.IsNormalSlot(), g.NoStorageErrors());
        }

        TString reply;
        g.GetInfo().LastPing = Now();
        TString host = TDatacenterUtil::Instance().GetPodIP(sd.FullHost());
        if (host == "")
            host = sd.FullHost();
        INFO_LOG << "check slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << "(" << host << ")" << Endl;
        if (NDaemonController::TControllerAgent(host, sd.ControllerPort()).ExecuteCommand("?command=get_status", reply, Config.TimeoutPing, Config.AttemptsPing, "")) {
            NJson::TJsonValue infoServer;
            if (!NJson::ReadJsonFastTree(reply, &infoServer)) {
                g.GetInfo().Status = NController::TSlotStatus::IncorrectReply;
            } else {
                g.GetInfo().ReadControllerReply(infoServer["result"]["server_status_global"]);
            }
        } else {
            INFO_LOG << "check slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << ": not available for connect" << Endl;
            g.GetInfo().Status = NController::TSlotStatus::Unavailable;
        }
        g.GetInfo().AddInfoByTime();
        if (IS_LOG_ACTIVE(TLOG_DEBUG)) {
            DEBUG_LOG << "check slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << ": " << g.GetInfo().SerializeToJson().GetStringRobust() << Endl;
        } else {
            INFO_LOG << "checked slot for " << sd.ShortSlotName() << "/" << sd.ControllerPort() << Endl;
        }
        return TSlotInfo(g.GetInfo(), g.IsNormalSlot(), g.NoStorageErrors());
    }
}
