#include "abstract.h"
#include <saas/library/daemon_base/protos/status.pb.h>
#include <saas/util/smartqueue.h>
#include <library/cpp/logger/global/global.h>
#include <util/string/cast.h>
#include <util/generic/typetraits.h>

namespace NRTYDeploy {
    IControllersChecker::TPingInfo::TPingInfo()
        : Success(0)
        , Fails(0)
    {}

    IControllersChecker::TPingInfo::TPingInfo(const NController::TPingInfoByTime& info)
        : Success(info.GetSuccess())
        , Fails(info.GetFails())
    {}

    NController::TPingInfoByTime IControllersChecker::TPingInfo::SerializeToProto() const {
        NController::TPingInfoByTime result;
        result.SetFails(Fails);
        result.SetSuccess(Success);
        return result;
    }

    NJson::TJsonValue IControllersChecker::TPingInfo::SerializeToJson() const {
        NJson::TJsonValue result;
        result.InsertValue("fails", Fails);
        result.InsertValue("success", Success);
        return result;
    }

    void IControllersChecker::TControllerStatus::AddInfoByTime() {
        ui32 time = Now().Hours();
        TPingInfo info;
        if (PingStatusesByTime.contains(time)) {
            info = PingStatusesByTime[time];
        } else {
            if (PingStatusesByTime.size() > 72) {
                PingStatusesByTime.erase(PingStatusesByTime.begin());
            }
        }
        if (Status == NController::Unavailable) {
            info.Fails++;
        } else {
            info.Success++;
        }
        PingStatusesByTime[time] = info;
    }

    TDuration IControllersChecker::TControllerStatus::GetNotAnsweredTime() const {
        if (LastPing.GetValue() >= LastReply.GetValue() && LastReply != TInstant::Zero())
            return LastPing - LastReply;
        return TDuration::Zero();
    }

    NJson::TJsonValue IControllersChecker::TControllerStatus::SerializeToJson() const {
        NJson::TJsonValue result;
        result["first_ping"] = FirstPing.GetValue();
        result["last_ping"] = LastPing.GetValue();
        result["last_reply"] = LastReply.GetValue();
        result["status"] = NController::TSlotStatus_Name(Status);
        result["info"] = Info;
        NJson::TJsonValue pingsInfo(NJson::JSON_MAP);
        for (auto&& i : PingStatusesByTime) {
            pingsInfo.InsertValue(ToString(i.first), i.second.SerializeToJson());
        }
        result["pings_info"] = std::move(pingsInfo);
        return result;
    }

    NController::TSlotChecker IControllersChecker::TControllerStatus::SerializeToProto() const {
        NController::TSlotChecker result;
        result.SetFirstPing(FirstPing.GetValue());
        result.SetLastPing(LastPing.GetValue());
        result.SetLastReply(LastReply.GetValue());
        result.SetStatus(Status);
        result.SetInfo(Info);
        for (auto&& i : PingStatusesByTime) {
            auto* pingInfo = result.AddPingInfoByTime();
            *pingInfo = i.second.SerializeToProto();
            pingInfo->SetHour(i.first);
        }
        return result;
    }

    IControllersChecker::TControllerStatus::TControllerStatus()
        : Status(NController::TSlotStatus::OK)
    {}

    IControllersChecker::TControllerStatus::TControllerStatus(const NController::TSlotChecker& value) {
        LastPing = TInstant::MicroSeconds(value.GetLastPing());
        FirstPing = TInstant::MicroSeconds(value.GetFirstPing());
        LastReply = TInstant::MicroSeconds(value.GetLastReply());
        Status = value.GetStatus();
        Info = value.GetInfo();
        for (ui32 i = 0; i < value.PingInfoByTimeSize(); ++i) {
            NController::TPingInfoByTime pingInfo = value.GetPingInfoByTime(i);
            PingStatusesByTime[pingInfo.GetHour()] = TPingInfo(pingInfo);
        }
    }

    void IControllersChecker::TControllerStatus::ReadControllerReply(const NJson::TJsonValue& value) {
        INFO_LOG << "TControllerStatus::ReadControllerReply for " << value.GetStringRobust() << Endl;
        if (value.Has("timestamp")) {
            LastReply = TInstant::MicroSeconds(value["timestamp"].GetIntegerRobust());
        }
        if (!value.Has("state") || !NController::TSlotStatus_Parse(value["state"].GetStringRobust(), &Status)) {
            Status = NController::TSlotStatus::IncorrectReply;
        }
        if (value.Has("info")) {
            Info = value["info"].GetStringRobust();
        }
    }

    IControllersChecker::IControllersChecker(const TDeployManagerConfig::TCheckerConfig& config)
        : Config(config)
    {}

    class IControllersChecker::TCheckingTask : public IObjectInQueue {
    public:
        TCheckingTask(const IControllersChecker& checker,
                      const NRTYCluster::TSlotData& slot,
                      TMutex& mutex,
                      TSlotsStates& result)
            : Checker(checker)
            , Slot(slot)
            , Mutex(mutex)
            , Result(result)
        {}

        void Process(void*) override {
            auto info = Checker.CheckSlot(Slot);
            TGuard<TMutex> g(Mutex);
            Result[Slot.ShortSlotName()] = info;
        };

    private:
        const IControllersChecker& Checker;
        const NRTYCluster::TSlotData& Slot;
        TMutex& Mutex;
        TSlotsStates& Result;
    };

    void IControllersChecker::CheckSlots(const TMap<TString, NRTYCluster::TSlotData>& slots, IControllersChecker::TSlotsStates& states) const {
        TMutex mutex;
        DEBUG_LOG << "Checking slots... " << Endl;
        TInstant start = TInstant::Now();
        NUtil::TSmartMtpQueue taskQueue;
        taskQueue.Start(Config.CheckerThreads);
        for (auto&& slot : slots) {
            CHECK_WITH_LOG(taskQueue.AddAndOwn(THolder(new TCheckingTask(*this, slot.second, mutex, states))));        }
        taskQueue.Stop();
        DEBUG_LOG << "Checking slots... " << TDuration(TInstant::Now() - start).MilliSeconds() << "ms" << Endl;
    }

    IControllersChecker::TFilter::TFilter(const IControllersChecker* checker)
        : Checker(checker)
    {
    }

    TMap<TString, NRTYCluster::TDatacenter> IControllersChecker::TFilter::Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const {
        if (!Checker)
            return dcs;
        TMap<TString, NRTYCluster::TDatacenter> result;
        for (auto&& dc : dcs) {
            TMap<TString, IControllersChecker::TSlotInfo> states;
            Checker->CheckSlots(dc.second.GetSlots(), states);
            for (auto&& slot : states) {
                if (slot.second.ControllerStatus.Status != NController::TSlotStatus::UnknownError) {
                    result[dc.first].RegisterSlot(dc.second.GetSlots().at(slot.first));
                }
            }
        }
        return result;
    }
}
