#include "module.h"

#include <balancer/modules/antirobot_common/checker_export.h>

#include <balancer/kernel/custom_io/chunkio.h>
#include <balancer/kernel/custom_io/concat.h>
#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/fs/shared_file_exists_checker.h>
#include <balancer/kernel/fs/shared_files.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/module/module.h>

#include <util/datetime/base.h>
#include <util/generic/ptr.h>
#include <util/generic/ylimits.h>

using namespace NConfig;
using namespace NSrvKernel;

Y_TLS(antirobot_wrapper) {
    TTls(bool cutRequest) : CutRequestDefault(cutRequest)
    {}

    bool CutRequest() const noexcept {
        if (!NoCutRequestFileChecker.Empty()) {
            return !NoCutRequestFileChecker.Exists();
        }

        return CutRequestDefault;
    }

    TSharedFileExistsChecker NoCutRequestFileChecker;
    bool CutRequestDefault = false;
};

MODULE_WITH_TLS(antirobot_wrapper) {
public:
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
    {
        Config->ForEach(this);

        if (!CutRequest_) {
            Y_WARN_ONCE("\"cut_request\" is not set. Will fail on big inputs.");
        }

        if (!Submodule_) {
            ythrow TConfigParseError() << "no module configured";
        }

        TryRethrowError(AntirobotRequest_.Parse("POST /fullreq HTTP/1.1\r\n\r\n"));
    }

private:
    START_PARSE {
        ON_KEY("cut_request", CutRequest_) {
            return;
        }

        ON_KEY("cut_request_bytes", CutRequestBytes_) {
            return;
        }

        ON_KEY("no_cut_request_file", NoCutRequestFile_) {
            return;
        }

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

    THolder<TTls> DoInitTls(IWorkerCtl* process) override {
        auto tls = MakeHolder<TTls>(CutRequest_);
        if (!!NoCutRequestFile_) {
            tls->NoCutRequestFileChecker = process->SharedFiles()->FileChecker(NoCutRequestFile_, TDuration::Seconds(1));
        }
        return tls;
    }

    TError DoRun(const TConnDescr& descr, TTls& tls) const noexcept override {
        const size_t cutRequestBytes = descr.Request && !descr.Request->Props().UpgradeRequested
            ? CutRequestBytes_ : 0;

        const bool cutRequest = descr.Request && !descr.Request->Props().UpgradeRequested
            ? tls.CutRequest() : true;

        NModAntiRobot::TChecker checker(Submodule_.Get(), descr, AntirobotRequest_, cutRequestBytes, true, cutRequest);

        Y_TRY(TError, error) {
            return checker.Run();
        } Y_CATCH {
            Y_UNUSED(SkipAll(descr.Input, TInstant::Max()));
            if (!error.GetAs<TBackendError>()) {
                descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "client error");
            }
            return error;
        }
        TError error = SkipAll(descr.Input, TInstant::Max());
        if (error) {
            descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "client read error");
        }
        return error;
    }

    void DoCheckConstraints() const override {
        if (!CheckParents([&](TStringBuf name) { return "h100" == name; })) {
            Y_WARN_ONCE("must have h100 as a parent");
        }
    }

private:
    THolder<IModule> Submodule_;
    TString NoCutRequestFile_;
    size_t CutRequestBytes_ = Max<size_t>();
    bool CutRequest_ = false;
    TRequest AntirobotRequest_;
};

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