#include "module.h"

#include <balancer/kernel/custom_io/limitio.h>
#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/memory/chunks.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/net/socket.h>
#include <balancer/kernel/ssl/sslio.h>

#include <util/generic/yexception.h>


using namespace NConfig;
using namespace NSrvKernel;
using namespace NModCutter;


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

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

private:
    START_PARSE {
        ON_KEY("timeout", Timeout_) {
            return;
        }

        ON_KEY("bytes", Bytes_) {
            return;
        }

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

    TError DoRun(const TConnDescr& descr) const noexcept override {
        if (descr.Input && descr.Request && !descr.Request->Props().UpgradeRequested) {
            const auto& messageProps = descr.Request->Props();

            if (messageProps.ChunkedTransfer || messageProps.ContentLength > 0) {
                Y_TRY(TError, error) {
                    return descr.Input->FillBuffer(Bytes_, Timeout_.ToDeadLine()).ReleaseError();
                } Y_CATCH {
                    descr.Properties->ConnStats.ClientError += 1;
                    if (const auto* e = error.GetAs<TSslError>()) {
                        descr.ExtraAccessLog << " bad_ssl_request";
                        descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "bad_ssl_request");
                        LOG_ERROR(TLOG_ERR, descr, "cutter: ssl read failed: " << e->what());
                        return error;
                    } else if (const auto* e = error.GetAs<TSystemError>()) {
                        if (e->Status() != ETIMEDOUT) {
                            descr.ExtraAccessLog << " bad_request";
                            descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "bad_request");
                            LOG_ERROR(TLOG_ERR, descr, "cutter: read failed: "
                                << TErrno(e->Status()) << ": " << e->what());
                            return error;
                        }
                    } else {
                        descr.ExtraAccessLog << " bad_request";
                        descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "bad_request");
                        LOG_ERROR(TLOG_ERR, descr, "cutter: read failed: "
                            << GetErrorMessage(error));
                        return error;
                    }
                }
            }
        }

        return Submodule_->Run(descr);
    }

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

    void DoCheckConstraints() const override {
        CheckParents([&](TStringBuf name) {
            if ("threshold" == name) {
                Y_WARN_ONCE("having threshold module as a parent. May cancel requests on timeout");
                return true;
            }
            return false;
        });
    }

private:
    TDuration Timeout_;
    size_t Bytes_ = 0;
};

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