#include "debug_io.h"
#include "module.h"

#include <balancer/kernel/helpers/misc.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/io/iobase.h>
#include <balancer/kernel/log/logbackend.h>
#include <balancer/kernel/memory/chunks.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/custom_io/stream.h>

#include <util/random/random.h>
#include <util/string/builder.h>

using namespace NSrvKernel;

MODULE_BASE(debug, TModuleWithSubModule) {
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
    {
        Config->ForEach(this);

        if (MinDelay_ && !RandomizeDelay_) {
            ythrow TConfigParseError() << "setting \"min_delay\" without \"randomize_delay\" has no sense";
        }
    }

    START_PARSE {
        ON_KEY("delay", RequestDelay_) {
            return;
        }

        ON_KEY("client_read_delay", ClientRead_.Delay) {
            return;
        }

        ON_KEY("client_read_size", ClientRead_.Size) {
            Y_ENSURE_EX(ClientRead_.Size > 0, TConfigParseError());
            return;
        }

        ON_KEY("client_write_delay", ClientWrite_.Delay) {
            return;
        }

        ON_KEY("client_write_size", ClientWrite_.Size) {
            Y_ENSURE_EX(ClientWrite_.Size > 0, TConfigParseError());
            return;
        }

        ON_KEY("min_delay", MinDelay_) {
            return;
        }

        ON_KEY("randomize_delay", RandomizeDelay_) {
            return;
        }

        ON_KEY("tag", Tag_) {
            return;
        }

        ON_KEY("fail_on_init", FailOnInit_) {
            return;
        }

        ON_KEY("freeze_on_run", FreezeOnRun_) {
            return;
        }

        ON_KEY("use_cpu", UseCpu_) {
            return;
        }

        ON_KEY("client_read_fail_after", ClientRead_.FailAfter) {
            return;
        }

        ON_KEY("client_write_fail_after", ClientWrite_.FailAfter) {
            return;
        }

        if (IsIn<TStringBuf>({"dump", "dlog", "probability", "build_info"}, key)) {
            // TODO(velavokr): remove completely
            return;
        }

        {
            Submodule_.Reset(Loader->MustLoad(key, Copy(value->AsSubConfig())).Release());
            return;
        }
    } END_PARSE

    TError DoRun(const TConnDescr& descr) const noexcept override {
        using namespace NModDebug;
        if (FreezeOnRun_) {
            while(true) {
            }
        }

        if (Tag_) {
            descr.ExtraAccessLog << ' ' << Tag_;
        }

        if (RequestDelay_.GetValue()) {
            if (TError error = CheckedSleepT(descr.Process().Executor().Running(), Delay())) {
                descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "cancelled");
                return error;
            }
        }

        Y_REQUIRE(Submodule_, TSystemError(ECONNREFUSED));

        if (ClientRead_ || ClientWrite_) {
            TDebugIn dbgi{ *descr.Input, descr.Process().Executor(), ClientRead_ };
            TDebugOut dbgo{ *descr.Output, descr.Process().Executor(), ClientWrite_ };

            Y_PROPAGATE_ERROR(Submodule_->Run(descr.Copy(dbgi, dbgo)));
        } else {
            Y_PROPAGATE_ERROR(Submodule_->Run(descr));
        }
        return {};
    }

    void DoInit(IWorkerCtl* process) override {
        if (FailOnInit_) {
            ythrow yexception() << "TModDebug::DoInit failed because fail_on_init is enabled";
        }
        if (UseCpu_) {
            UseCpuCont_ = TCoroutine{"use_cpu_cont", &process->Executor(), [executor = &process->Executor()]() {
                auto cont = executor->Running();
                double s = 1, d = 1, n = 1;
                do {
                    s += 1. / d;
                    d *= ++n;
                } while (cont->SleepT(TDuration::MilliSeconds(5)) == ETIMEDOUT);
            }};
        }
    }

    bool DoExtraAccessLog() const noexcept override {
        return !Tag_.empty();
    }

    bool DoCanWorkWithoutHTTP() const noexcept override {
        return true;
    }

    TDuration Delay() const noexcept {
        if (RandomizeDelay_) {
            return MinDelay_ + RequestDelay_ * RandomNumber<double>();
        }

        return RequestDelay_;
    }

private:
    TString Tag_;
    TDuration MinDelay_;
    TDuration RequestDelay_;
    NModDebug::TDebugIoParams ClientRead_;
    NModDebug::TDebugIoParams ClientWrite_;
    TCoroutine UseCpuCont_;

    bool RandomizeDelay_ = false;
    bool FailOnInit_ = false;
    bool FreezeOnRun_ = false;
    bool UseCpu_ = false;
};

IModuleHandle* NModDebug::Handle() {
    return TModule::Handle();
}
