#include "quota_syncer_process.h"

#include <balancer/kernel/http/parser/request_builder.h>
#include <balancer/kernel/ctl/children_process_common.h>
#include <balancer/kernel/dns/dns_helper.h>
#include <balancer/kernel/requester/requester.h>

#include <util/generic/maybe.h>
#include <util/folder/dirut.h>
#include <util/system/fs.h>


namespace NSrvKernel::NProcessCore {
    using namespace NRpsLimiter;
    void TQuotaSyncerProcess::SyncLoop(TString peerName) {
        TRequest request = BuildRequest().Method(EMethod::POST).Version11().Path(QuotaManager_.QuotaSyncPath);

        while (!Executor_->Executor().Running()->Cancelled()) {
            Executor_->Executor().Running()->SleepT(
                QuotaManager_.QuotaSyncInterval * (0.9 + 0.2 * RandomNumber<double>())
            );
            TAsyncRequester requester(*QuotaManager_.Remote[peerName], nullptr, *this, 0, TInstant::Now());

            TString state = RenderPeerQuotas(QuotaManager_.Storage.GenPeerQuotasFromLocal(TInstant::Now()));

            Y_TRY (TError, error) {
                TResponse response;
                TChunkList responseBody;
                Y_PROPAGATE_ERROR(requester.Requester().Request(
                    TRequest(request), TChunkList(std::move(state)), false, response, responseBody));
                TPeerQuotas hq;
                Y_PROPAGATE_ERROR(ParsePeerQuotas(Union(responseBody)->AsStringBuf()).AssignTo(hq));
                // TODO(velavokr): do not ignore
                Y_UNUSED(QuotaManager_.Storage.UpdatePeerQuotas(hq, TInstant::Now()));
                return {};
            } Y_CATCH {
                // TODO(velavokr): Logging!
                continue;
            };
        };
    }

    TQuotaSyncerProcess::TQuotaSyncerProcess(TMainTask& task, const TMainOptions& options, TW2WChannel<TM2CMessage>& m2cChannel)
        : TBaseProcess(task.Config().NChildren + ChildProcessTypeToIndex(WorkerType()), task)
        , MainStats_(MakeHolder<TMainStats>(*task.MainStats_, WorkerId()))
        , M2CChannel_(m2cChannel)
        , DnsHelper_(MakeHolder<NDns::THelper>())
        , QuotaManager_(*task.QuotaManager_)
    {
        Y_ENSURE_EX(task.QuotaManager_,
            TConfigParseError() << "rpslimiter_instance required");

        const auto& config = task.Config();

        WorkerCpuStat_ = task.ProcessStatOwner_->GetProcessStat(WorkerType())->GetWorkerCpuStat(WorkerId());

        Executor_ = MakeHolder<TOwnExecutor>(
            options.CoroStackSize(),
            options.Poller,
            options.CoroStackGuard,
            options.CoroPoolSettings,
            WorkerCpuStat_.Get()
        );
        Executor_->Executor().SetFailOnError(config.CoroFailOnError);

        CpuMeasureCoroutine_ = StartCpuAndTimeMeasurer(Executor_->Executor(), WorkerCpuStat_.Get(), nullptr);

        RecvLoopTask_ = TCoroutine("quota_syncer_recv_loop", &Executor_->Executor(),
                &TQuotaSyncerProcess::RecvLoop, this);

        for (auto&& h : QuotaManager_.Remote) {
            if (h.first == QuotaManager_.Storage.SelfName()) {
                continue;
            }

            TString taskName = "quota_sync_task_" + h.first;
            QuotaSyncTasks_.emplace_back(
                taskName.Data(), &Executor_->Executor(), &TQuotaSyncerProcess::SyncLoop, this, h.first);
        }

        SharedFiles_ = MakeHolder<TSharedFiles>();

        DnsCounters_ = task.ProcessStatOwner_->GetProcessStat(WorkerType())->GetDnsCounters(WorkerId());
        const bool useAsync = config.Dns.AsyncResolve;
        DnsHelper_->CreateDnsResolver(&Executor_->Executor(), SharedFiles_.Get(), config.Dns,
                DnsCounters_.Get(), useAsync, ThreadedQueue("resolver_queue"));

        task.Trees.Init(this);
    }

    TQuotaSyncerProcess::~TQuotaSyncerProcess() {
        Dispose();
    }

    void TQuotaSyncerProcess::Execute() {
        SharedFiles_->Start();
        Executor_->Executor().Execute();
    }

    void TQuotaSyncerProcess::Stop() {
        HealthCheckLoopTask_.Cancel();
        RecvLoopTask_.Cancel();
        Dispose();
    }

    void TQuotaSyncerProcess::RecvLoop() {
        auto runningCont = Executor_->Executor().Running();
        auto waker = ThreadLocalEventWaker(runningCont);
        auto* wakerPtr = waker.Get();

        bool shutdown = false;
        while (!runningCont->Cancelled() && !shutdown) {
            TM2CMessage message;
            auto status = M2CChannel_.Receive(message, TInstant::Max(), runningCont, wakerPtr);
            if (status != EChannelStatus::Success) {
                return;
            }

            std::visit(TOverloaded{
                [&](const TEvent& masterEvent) {
                    Y_UNUSED(masterEvent.OutputChannel->Send("", TInstant::Max(), runningCont, wakerPtr));
                },

                [&](const TListEvents& toMaster) {
                    Y_UNUSED(toMaster.OutputChannel->Send(TVector<TString>{}, TInstant::Max(), runningCont, wakerPtr));
                },

                [&](const TShutDown&) {
                    Stop();
                    shutdown = true;
                },

                [&](const TResetDnsCache&) {
                }
            }, message);
        }
    }

    TSharedFiles* TQuotaSyncerProcess::SharedFiles() noexcept {
        return SharedFiles_.Get();
    }

    TThreadedQueue* TQuotaSyncerProcess::ThreadedQueue(const TString &name) noexcept {
        return ThreadedQueueList_.FindOrCreate(
            &Executor_->Executor(),
            ThreadPool_,
            name
        );
    }

    NSrvKernel::TPingerManager& TQuotaSyncerProcess::SharedPingerManager() noexcept {
        Y_FAIL();
    }

    TLog* TQuotaSyncerProcess::GetLog(const TString&) {
        return nullptr;
    }

    NProcessCore::TChildProcessType TQuotaSyncerProcess::WorkerType() const noexcept {
        return NProcessCore::TChildProcessType::QuotaSyncer;
    }

    NDns::IResolver& TQuotaSyncerProcess::Resolver() noexcept {
        return DnsHelper_->Resolver();
    }

    void TQuotaSyncerProcess::DoDispose() noexcept {
        for (auto i = ThreadedQueueList_.Begin(); i != ThreadedQueueList_.End(); ++i) {
            i->Dispose();
        }

        for (auto i = ThreadedQueueList_.Begin(); i != ThreadedQueueList_.End(); ++i) {
            i->Join();
        }

        Executor_->Executor().Abort();
    }

    TLog* TQuotaSyncerProcess::GetDynamicBalancingLog() {
        return nullptr;
    }
}
