#include "module.h"

#include <balancer/kernel/coro/coro_async.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/regexp/regexp_re2.h>
#include <balancer/kernel/requester/requester.h>

#include <kernel/querydata/idl/querydata_structs.pb.h>
#include <search/idl/meta.pb.h>

using namespace NConfig;

namespace NSrvKernel {

bool FindThumbId(const TChunkList& contentList, const TStringBuf& id) noexcept {
    auto content = Union(contentList);
    NMetaProtocol::TReport report;
    if (!report.ParseFromArray(content->Data(), content->Length())) {
        return false;
    }

    for (size_t i = 0; i < report.SearcherPropSize(); ++i) {
        NQueryData::TQueryData qd;
        const TString& searcherProp = report.GetSearcherProp(i).GetValue();
        if (!qd.ParseFromArray(searcherProp.data(), searcherProp.size())) {
            return false;
        }
        for (size_t j = 0; j < qd.SourceFactorsSize(); ++j) {
            const NQueryData::TSourceFactors& sf = qd.GetSourceFactors(j);
            if (!id.compare(sf.GetSourceKey())) {
                return true;
            }
        }
    }
    return false;
}

MODULE(thumbsban) {
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
    {
        Config->ForEach(this);

        if (!Regexp_) {
            ythrow TConfigParseError() << "no regexp configured";
        }

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

        if (!Checker_) {
            ythrow TConfigParseError() << "no checker configured";
        }

        if (!BanHandler_) {
            ythrow TConfigParseError() << "no ban handler configured";
        }
    }

private:
    TErrorOr<bool> CheckBanned(TStringBuf capturedGroup, IWorkerCtl& process) const {
        TAsyncRequester requester{*Checker_, nullptr, process};

        TRequest request;
        {
            TString requestString;
            requestString += "GET /yandsearch?ms=proto&text=";
            requestString += TString(capturedGroup);
            requestString += " HTTP/1.1\r\n\r\n";
            Y_PROPAGATE_ERROR(request.Parse(std::move(requestString)));
        }
        TResponse response;
        TChunkList contentList;
        Y_PROPAGATE_ERROR(requester.Requester().Request(std::move(request), response, contentList));

        return FindThumbId(contentList, capturedGroup);
    }

    START_PARSE {
        TString idRegExp;
        ON_KEY ("id_regexp", idRegExp) {
            THolder<TRegexp>(new TRegexp(idRegExp)).Swap(Regexp_);
            return;
        }

        if (key == "checker") {
            TSubLoader(Copy(value->AsSubConfig())).Swap(Checker_);
            return;
        }

        if (key == "module") {
            TSubLoader(Copy(value->AsSubConfig())).Swap(Module_);
            return;
        }

        if (key == "ban_handler") {
            TSubLoader(Copy(value->AsSubConfig())).Swap(BanHandler_);
            return;
        }
    } END_PARSE

    TError DoRun(const TConnDescr& descr) const noexcept override {
        LOG_ERROR(TLOG_INFO, descr, "thumbsban started");

        auto bannedFut = CoroAsync("ban_checker", &descr.Process().Executor(), [&]() {
            TVector<TStringBuf> capturedGroups;
            bool ret = false;

            if (Regexp_->Extract(
                descr.Request->RequestLine().CGI.AsStringBuf(), &capturedGroups, true) &&
                capturedGroups.size() > 1)
            {
                Y_UNUSED(CheckBanned(capturedGroups[1], descr.Process()).AssignTo(ret));
            }
            return ret;
        });

        // TODO: make limit for responseBody
        TResponse moduleResponse;
        TChunkList responseBody;

        auto moduleFut = CoroAsync("module_cont", &descr.Process().Executor(), [&] {
            auto output = MakeHttpOutput([&](TResponse&& response, bool forceClose, TInstant) {
                Y_UNUSED(forceClose);
                moduleResponse = std::move(response);
                return TError{};
            }, [&](TChunkList lst, TInstant) {
                responseBody.Append(std::move(lst));
                return TError{};
            }, [](THeaders&&, TInstant) {
                return TError{};
            });
            return Module_->Run(descr.CopyOut(output));
        });

        if (bannedFut.Get()) {
            Y_DEFER {
                descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "request banned");
            };
            Y_PROPAGATE_ERROR(BanHandler_->Run(descr));
            LOG_ERROR(TLOG_INFO, descr, "request banned");
            return {};
        }
        Y_PROPAGATE_ERROR(moduleFut.Get());

        Y_TRY(TError, error) {
            Y_PROPAGATE_ERROR(descr.Output->SendHead(std::move(moduleResponse), false, TInstant::Max()));
            if (!responseBody.Empty()) {
                Y_PROPAGATE_ERROR(descr.Output->Send(std::move(responseBody), TInstant::Max()));
            }
            return descr.Output->SendEof(TInstant::Max());
        } Y_CATCH {
            descr.ExtraAccessLog.SetSummary(GetHandle()->Name(), "client write error");
            return error;
        };
        LOG_ERROR(TLOG_INFO, descr, "request not banned");
        return {};
    }

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

private:
    THolder<IModule> Module_;
    THolder<IModule> Checker_;
    THolder<IModule> BanHandler_;
    THolder<TRegexp> Regexp_;
};

}  // namespace NSrvKernel

NSrvKernel::IModuleHandle* NModThumbsBan::Handle() {
    return TModule::Handle();
}
