#include "module.h"

#include <balancer/kernel/http/parser/request_builder.h>
#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/matcher/matcher.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/regexp/regexp_re2.h>

#include <antiadblock/libs/decrypt_url/lib/cry.h>
#include <library/cpp/json/json_reader.h>
#include <library/cpp/uri/uri.h>

using namespace NSrvKernel;
using namespace NModCryprox;

namespace NModCryprox {
    class TLimitedBuffer {
    public:
        TLimitedBuffer(size_t capacity) : Capacity_(capacity) {}

        bool Add(TChunkList& lst) {
            size_t size = lst.size();
            if (Size_ > 0 && Size_ + size > Capacity_) {
                return false;
            }
            Size_ += size;
            Buffer_.Append(std::move(lst));
            return true;
        }

        bool AddCopy(const TChunkList& lst) {
            size_t size = lst.size();
            if (Size_ > 0 && Size_ + size > Capacity_) {
                return false;
            }
            Size_ += size;
            Buffer_.Append(lst.Copy());
            return true;
        }

        TChunkList Get() {
            Size_ = 0;
            return std::exchange(Buffer_, TChunkList{});
        }

        bool Empty() {
            return Size_ == 0;
        }

        void Clear() {
            Size_ = 0;
            Buffer_.Clear();
        }

    private:
        TChunkList Buffer_;
        size_t Size_ = 0;
        size_t Capacity_ = 0;
    };

    enum class ERewindState {
        ALLOW_REWIND,
        NO_REWIND,
        NEED_TO_FLUSH,
        PROXY_TO_CLIENT,
    };

    class TFromBackendTransfer : public IIoInput, public IIoOutput {
    public:
        TFromBackendTransfer(size_t streamingBufferSize, size_t rewindBufferSize)
            : SteamingBuffer_(streamingBufferSize), RewindBuffer_(rewindBufferSize)
        {}

        TErrorOr<bool> TryToRewind(
            TResponse&& backendResponse, bool backendForceClose, IHttpOutput* rewindOutput
        ) noexcept {
            if (RewindState_ != ERewindState::ALLOW_REWIND) {
                return false;
            }

            BackendResponse_ = std::move(backendResponse);
            BackendForceClose_ = backendForceClose;
            RewindOutput_ = rewindOutput;

            if (Eof_) {
                RewindState_ = ERewindState::PROXY_TO_CLIENT;
                Y_PROPAGATE_ERROR(FlushRewindBuffer(TInstant::Max()));
                Y_PROPAGATE_ERROR(RewindOutput_->SendEof(TInstant::Max()));
            } else {
                RewindState_ = ERewindState::NEED_TO_FLUSH;
                FullBufferCV_.notify();
            }

            return true;
        }

        void NoRewind() {
            Y_ASSERT(RewindState_ == ERewindState::ALLOW_REWIND || RewindState_ == ERewindState::NO_REWIND);
            if (RewindState_ != ERewindState::ALLOW_REWIND) {
                return;
            }
            RewindState_ = ERewindState::NO_REWIND;
            RewindBuffer_.Clear();
        }

        bool RewindBufferOverflow() const noexcept {
            return RewindBufferOverflow_;
        }

    private:
        TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
            if (RewindState_ == ERewindState::NEED_TO_FLUSH) {
                RewindState_ = ERewindState::PROXY_TO_CLIENT;
                Y_PROPAGATE_ERROR(FlushRewindBuffer(deadline));
            }

            if (RewindState_ == ERewindState::PROXY_TO_CLIENT) {
                return RewindOutput_->Send(std::move(lst), deadline);
            }

            if (!lst.Empty()) {
                if (RewindState_ == ERewindState::ALLOW_REWIND) {
                    if (!RewindBuffer_.AddCopy(lst)) {
                        RewindBufferOverflow_ = true;
                        NoRewind();
                    }
                }

                if (!SteamingBuffer_.Add(lst)) {
                    const int ret = FullBufferCV_.wait_until(RunningCont()->Executor(), deadline);
                    Y_REQUIRE(ret == EWAKEDUP, TSystemError{ret});

                    if (RewindState_ == ERewindState::NEED_TO_FLUSH) {
                        RewindState_ = ERewindState::PROXY_TO_CLIENT;
                        Y_PROPAGATE_ERROR(FlushRewindBuffer(deadline));
                    }

                    if (RewindState_ == ERewindState::PROXY_TO_CLIENT) {
                        return RewindOutput_->Send(std::move(lst), deadline);
                    }

                    bool added = SteamingBuffer_.Add(lst);
                    Y_ASSERT(added);
                }
            } else {
                Eof_ = true;
            }
            EmptyBufferCV_.notify();

            return {};
        }

        TError DoRecv(TChunkList& lst, TInstant deadline) noexcept override {
            Y_ASSERT(RewindState_ == ERewindState::ALLOW_REWIND || RewindState_ == ERewindState::NO_REWIND);
            if (RewindState_ != ERewindState::ALLOW_REWIND && RewindState_ != ERewindState::NO_REWIND) {
                return {};
            }

            if (SteamingBuffer_.Empty() && !Eof_) {
                const int ret = EmptyBufferCV_.wait_until(RunningCont()->Executor(), deadline);
                Y_REQUIRE(ret == EWAKEDUP, TSystemError{ret});
            }

            if (!SteamingBuffer_.Empty()) {
                lst.Append(SteamingBuffer_.Get());
                FullBufferCV_.notify();
            }

            return {};
        }

        TError FlushRewindBuffer(TInstant deadline) {
            Y_PROPAGATE_ERROR(RewindOutput_->SendHead(std::move(BackendResponse_), BackendForceClose_, deadline));
            if (!RewindBuffer_.Empty()) {
                Y_PROPAGATE_ERROR(RewindOutput_->Send(RewindBuffer_.Get(), deadline));
            }
            return {};
        }

    private:
        TLimitedBuffer SteamingBuffer_;
        TLimitedBuffer RewindBuffer_;

        TCoroSingleCondVar EmptyBufferCV_;
        TCoroSingleCondVar FullBufferCV_;

        IHttpOutput* RewindOutput_ = nullptr;
        TResponse BackendResponse_;
        bool BackendForceClose_ = false;

        ERewindState RewindState_ = ERewindState::ALLOW_REWIND;
        bool Eof_ = false;
        bool RewindBufferOverflow_ = false;
    };

    class TTrailersSender {
    public:
        TTrailersSender(IHttpOutput* output) : Output_(output) {}

        TError Send(THeaders&& trailers, TInstant deadline) {
            Y_ASSERT(!Sent_);
            if (Sent_) {
                return {};
            }
            Sent_ = true;

            if (Flushed_) {
                return Output_->SendTrailers(std::move(trailers), deadline);
            } else {
                Trailers_ = std::move(trailers);
            }
            return {};
        }

        TError Flush(TInstant deadline) {
            if (Flushed_) {
                return {};
            }
            Flushed_ = true;

            if (Sent_) {
                return Output_->SendTrailers(std::move(Trailers_), deadline);
            }
            return {};
        }

    private:
        THeaders Trailers_;
        IHttpOutput* Output_;
        bool Sent_ = false;
        bool Flushed_ = false;
    };

    class TAccessLogBuffer final : public IOutputStream {
    public:
        TAccessLogBuffer(IOutputStream* parent) : Parent_(parent) {}

        ~TAccessLogBuffer() {
            Parent_->Write(TStringBuf(Buffer_.Data(), Buffer_.Filled()));
        }

    private:
        void DoWrite(const void* buf, size_t len) {
            Buffer_.Write(buf, len);
        }

        TTempBufOutput Buffer_;
        IOutputStream* Parent_;
    };

    struct TModuleStats {
        TModuleStats(TSharedStatsManager& statsManager)
            : Cycle(statsManager.MakeCounter("cryprox-cycle").AllowDuplicate().Build())
            , DecodeError(statsManager.MakeCounter("cryprox-decode_error").AllowDuplicate().Build())
            , CryproxTest(statsManager.MakeCounter("cryprox-cryprox_test").AllowDuplicate().Build())
            , ForwardToCryprox(statsManager.MakeCounter("cryprox-forward_to_cryprox").AllowDuplicate().Build())
            , ForwardToBackend(statsManager.MakeCounter("cryprox-forward_to_backend").AllowDuplicate().Build())
            , UnableToParseUrl(statsManager.MakeCounter("cryprox-unable_to_parse_url").AllowDuplicate().Build())
            , UseCryprox(statsManager.MakeCounter("cryprox-use_cryprox").AllowDuplicate().Build())
            , Rewind(statsManager.MakeCounter("cryprox-rewind").AllowDuplicate().Build())
            , UnableToRewind(statsManager.MakeCounter("cryprox-unable_to_rewind").AllowDuplicate().Build())
            , RewindBufferOverflow(statsManager.MakeCounter("cryprox-rewind_buffer_overflow").AllowDuplicate().Build())
            , UpdateSecrets(statsManager.MakeCounter("cryprox-update_secrets").AllowDuplicate().Build())
            , BadSecretsFile(statsManager.MakeCounter("cryprox-bad_secrets_file").AllowDuplicate().Build())
        {}

        TModuleStats(TModuleStats& holders, size_t workerId)
            : Cycle(holders.Cycle, workerId)
            , DecodeError(holders.DecodeError, workerId)
            , CryproxTest(holders.CryproxTest, workerId)
            , ForwardToCryprox(holders.ForwardToCryprox, workerId)
            , ForwardToBackend(holders.ForwardToBackend, workerId)
            , UnableToParseUrl(holders.UnableToParseUrl, workerId)
            , UseCryprox(holders.UseCryprox, workerId)
            , Rewind(holders.Rewind, workerId)
            , UnableToRewind(holders.UnableToRewind, workerId)
            , RewindBufferOverflow(holders.RewindBufferOverflow, workerId)
            , UpdateSecrets(holders.UpdateSecrets, workerId)
            , BadSecretsFile(holders.BadSecretsFile, workerId)
        {}

        TSharedCounter Cycle;
        TSharedCounter DecodeError;
        TSharedCounter CryproxTest;
        TSharedCounter ForwardToCryprox;
        TSharedCounter ForwardToBackend;
        TSharedCounter UnableToParseUrl;
        TSharedCounter UseCryprox;
        TSharedCounter Rewind;
        TSharedCounter UnableToRewind;
        TSharedCounter RewindBufferOverflow;
        TSharedCounter UpdateSecrets;
        TSharedCounter BadSecretsFile;
    };

    const TStringBuf FROM_CLIENT_TO_CRYPROX[]  = {"x-req-id", "accept-encoding", "cookie"};
    const TStringBuf FROM_BACKEND_TO_CRYPROX[] = {
        "content-encoding", "content-type", "content-security-policy", "content-security-policy-report-only"
    };
    const TStringBuf FROM_CRYPROX_TO_CLIENT[]  = {
        "content-encoding", "content-security-policy", "content-security-policy-report-only"
    };

    struct TSecrets {
        TString CryptSecretKey;
        TString CryptPreffixes;
        bool CryptEnableTrailingSlash = false;
        TMaybe<TRegexp> BackendUrlMatcherHolder;
    };

    TSecrets ParseSecrets(const NJson::TJsonValue& secrets) {
        TSecrets ret;
        const auto& secretsMap = secrets.GetMapSafe();

        const auto cryptSecretKeyIt = secretsMap.find("crypt_secret_key");
        Y_ENSURE_EX(cryptSecretKeyIt != secretsMap.end(), TConfigParseError() << "no crypt_secret_key");
        ret.CryptSecretKey = cryptSecretKeyIt->second.GetStringSafe();

        const auto cryptPreffixesIt = secretsMap.find("crypt_preffixes");
        Y_ENSURE_EX(cryptPreffixesIt != secretsMap.end(), TConfigParseError() << "no crypt_preffixes");
        ret.CryptPreffixes = cryptPreffixesIt->second.GetStringSafe();

        const auto cryptEnableTrailingSlashIt = secretsMap.find("crypt_enable_trailing_slash");
        Y_ENSURE_EX(cryptEnableTrailingSlashIt != secretsMap.end(), TConfigParseError() << "no crypt_enable_trailing_slash");
        ret.CryptEnableTrailingSlash = cryptEnableTrailingSlashIt->second.GetBooleanSafe();

        const auto backendUrlREIt = secretsMap.find("backend_url_re");
        if (backendUrlREIt != secretsMap.end()) {
            ret.BackendUrlMatcherHolder = MakeMaybe<TRegexp>(backendUrlREIt->second.GetStringSafe(), TRegexp::TOpts().SetCaseInsensitive(true).SetPosixSyntax(false));
        }

        return std::move(ret);
    }
} // namespace NModCryprox

Y_TLS(cryprox) {
    TTls(TModuleStats& holders, size_t workerId) : Stats(holders, workerId) {}

    TSharedFileReReader SecretsFileReReader;
    TSharedFileExistsChecker DisableFileChecker;

    TSharedFileReReader::TData SecretsFileData;
    TSecrets Secrets;
    TRegexp* BackendUrlMatcher = nullptr;

    TModuleStats Stats;
};

MODULE_WITH_TLS_BASE(cryprox, TModuleWithSubModule) {
public:
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
        , SharedStats_(mp.Control->SharedStatsManager())
        , ServiceBackend_(Submodule_)
    {
        Config->ForEach(this);

        Y_ENSURE_EX(PartnerToken_, TConfigParseError() << "no partner token configured");
        Y_ENSURE_EX(SecretsFile_, TConfigParseError() << "no secrets file configured");
        Y_ENSURE_EX(UseCryproxMatcher_, TConfigParseError() << "no cryprox matcher configured");
        Y_ENSURE_EX(CryproxBackend_, TConfigParseError() << "no cryprox backend configured");
        Y_ENSURE_EX(ServiceBackend_, TConfigParseError() << "no service backend configured");

        TFileInput secretsFile(SecretsFile_);
        NJson::TJsonValue secrets;
        NJson::ReadJsonTree(&secretsFile, &secrets);
        StartSecrets_ = ParseSecrets(secrets);
    }

    private:
        START_PARSE {
            ON_KEY("partner_token", PartnerToken_) {
                return;
            }

            ON_KEY("secrets_file", SecretsFile_) {
                return;
            }

            ON_KEY("disable_file", DisableFile_) {
                return;
            }

            ON_KEY("streaming_buffer_size", StreamingBufferSize_) {
                return;
            }

            ON_KEY("rewind_buffer_size", RewindBufferSize_) {
                return;
            }

            if (key == "use_cryprox_matcher") {
                ParseMap(value->AsSubConfig(), [&](auto subKey, auto* subValue) {
                    Y_ENSURE_EX(!UseCryproxMatcher_, TConfigParseError() << "several cryprox matchers");
                    UseCryproxMatcher_ = ConstructRequestMatcher(Control, subKey, subValue->AsSubConfig());
                });
                return;
            }

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

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

    THolder<TTls> DoInitTls(IWorkerCtl* process) override {
        auto tls = MakeHolder<TTls>(SharedStats_, process->WorkerId());
        tls->SecretsFileReReader = process->SharedFiles()->FileReReader(SecretsFile_, TDuration::Seconds(1));
        if (!!DisableFile_) {
            tls->DisableFileChecker = process->SharedFiles()->FileChecker(DisableFile_, TDuration::Seconds(1));
        }
        tls->Secrets = {
            .CryptSecretKey = StartSecrets_.CryptSecretKey,
            .CryptPreffixes = StartSecrets_.CryptPreffixes,
            .CryptEnableTrailingSlash = StartSecrets_.CryptEnableTrailingSlash,
        };
        tls->BackendUrlMatcher = StartSecrets_.BackendUrlMatcherHolder.Get();
        return tls;
    }

    void UpdateSecrets(const TConnDescr& descr, TTls& tls) const {
        const auto& data = tls.SecretsFileReReader.Data();
        if (data.Id() == tls.SecretsFileData.Id()) {
            return;
        }
        ++tls.Stats.UpdateSecrets;
        tls.SecretsFileData = data;

        try {
            NJson::TJsonValue secrets;
            NJson::ReadJsonTree(data.Data(), &secrets);
            tls.Secrets = ParseSecrets(secrets);
            tls.BackendUrlMatcher = tls.Secrets.BackendUrlMatcherHolder.Get();
        } catch (...) {
            ++tls.Stats.BadSecretsFile;
            LOG_ERROR(TLOG_ERR, descr, "UpdateSecrets: Bad secrets file: " << data.Data() << ":" << CurrentExceptionMessage());
        }
    }

    TError DoRun(const TConnDescr& descr, TTls& tls) const noexcept override {
        UpdateSecrets(descr, tls);

        if (descr.Request->Headers().GetFirstValue("x-aab-proxy")) {
            descr.ExtraAccessLog << " cryprox cycle";
            ++tls.Stats.Cycle;
            return ServiceBackend_->Run(descr);
        }

        NAntiAdBlock::TDecryptResult decryptResult;

        TError decodeError = [&] () -> TError {
            try {
                decryptResult = NAntiAdBlock::DecryptUrl(
                    descr.Request->RequestLine().Path.AsStringBuf(),
                    tls.Secrets.CryptSecretKey, tls.Secrets.CryptPreffixes, tls.Secrets.CryptEnableTrailingSlash
                );
            } Y_TRY_STORE(yexception);
            return {};
        }();

        if (decodeError) {
            descr.ExtraAccessLog << " cryprox decode error";
            ++tls.Stats.DecodeError;
        }

        if (descr.Request->Headers().GetFirstValue("x-aab-http-check") || descr.Request->Headers().GetFirstValue("x-aab-jstracer")) {
            descr.ExtraAccessLog << " cryprox test";
            ++tls.Stats.CryproxTest;
            return ForwardToCryprox(descr);
        }

        NUri::TUri parsedBackendUrl;
        TMaybe<TRequest> backendRequest;

        if (decryptResult.url) {
            descr.ExtraAccessLog << " decrypted:<<" << decryptResult.url << ">>";

            if (parsedBackendUrl.Parse(
                decryptResult.url, NUri::TFeature::FeaturesDefault | NUri::TFeature::FeatureSchemeKnown
            ) == NUri::TState::ParsedOK) {
                if (tls.BackendUrlMatcher && tls.BackendUrlMatcher->Match(
                    TStringBuilder{} << parsedBackendUrl.GetField(NUri::TField::FieldHost) << parsedBackendUrl.GetField(NUri::TField::FieldPath)
                )) {
                    ++tls.Stats.ForwardToBackend;
                    backendRequest = *descr.Request;
                    backendRequest->RequestLine().Path = TStringStorage(parsedBackendUrl.GetField(NUri::TField::FieldPath));
                }
            } else {
                ++tls.Stats.UnableToParseUrl;
            }

            if (!backendRequest) {
                descr.ExtraAccessLog << " cryprox forward";
                ++tls.Stats.ForwardToCryprox;
                return ForwardToCryprox(descr);
            }
        }

        if (!decryptResult.url && !UseCryproxMatcher_->Match(descr)) {
            descr.ExtraAccessLog << " no cryprox";
            return ServiceBackend_->Run(descr);
        }

        if (tls.DisableFileChecker.Exists()) {
            descr.ExtraAccessLog << " cryprox disable file";
            if (backendRequest) {
                auto newDescr = descr.Copy();
                newDescr.Request = backendRequest.Get();
                return ServiceBackend_->Run(newDescr);
            } else {
                return ServiceBackend_->Run(descr);
            }
        }

        ++tls.Stats.UseCryprox;
        TResponse backendResponse;
        bool backendForceClose = false;

        bool useCryprox = false;

        TFromBackendTransfer fromBackendTranser(StreamingBufferSize_, RewindBufferSize_);
        TTrailersSender trailersSender(descr.Output);
        TError cryproxError;

        TAccessLogBuffer cryproxAccessLog(descr.ExtraAccessLog.Slave());

        TCoroutine cryproxCont;

        auto toCryproxOutput = MakeHttpOutput([&](TResponse&& response, const bool forceClose, TInstant deadline) {
            useCryprox = response.ResponseLine().StatusCode == 200;
            if (!useCryprox) {
                return descr.Output->SendHead(std::move(response), forceClose, deadline);
            }

            backendResponse = std::move(response);
            backendForceClose = forceClose;

            cryproxCont = TCoroutine({"send_cryprox_request", RunningCont()->Executor(), [&] {
                SendCryproxRequest(
                    descr, tls, decryptResult, cryproxError,
                    fromBackendTranser, trailersSender,
                    backendResponse, backendForceClose, cryproxAccessLog
                );
            }});

            return TError{};
        }, [&](TChunkList lst, TInstant deadline) {
            if (!useCryprox) {
                return descr.Output->Send(std::move(lst), deadline);
            }
            if (cryproxError) {
                return std::exchange(cryproxError, TError{});
            }
            return fromBackendTranser.Send(std::move(lst), deadline);
        }, [&](THeaders&& trailers, TInstant deadline) -> TError {
            if (!useCryprox) {
                return descr.Output->SendTrailers(std::move(trailers), deadline);
            }
            if (cryproxError) {
                return std::exchange(cryproxError, TError{});
            }
            return trailersSender.Send(std::move(trailers), deadline);
        });

        auto newDescr = descr.CopyOut(toCryproxOutput);
        if (backendRequest) {
            newDescr.Request = backendRequest.Get();
        }

        const TExtraAccessLogEntry logEntry(descr, "service_backend");
        Y_PROPAGATE_ERROR(ServiceBackend_->Run(newDescr));

        cryproxCont.Join();

        return std::move(cryproxError);
    }

    TError ForwardToCryprox(const TConnDescr& descr) const {
        auto newDescr = descr.Copy();

        TRequest request = *descr.Request;
        request.Headers().Add("x-aab-partnertoken", PartnerToken_);
        request.Headers().Add("x-forwarded-proto", descr.Properties->UserConnIsSsl ? "https" : "http");
        newDescr.Request = &request;

        return CryproxBackend_->Run(newDescr);
    }

    void SendCryproxRequest(
        const TConnDescr& descr, TTls& tls, const NAntiAdBlock::TDecryptResult& decryptResult, TError& cryproxError,
        TFromBackendTransfer& fromBackendTranser, TTrailersSender& trailersSender,
        TResponse& backendResponse, bool backendForceClose, TAccessLogBuffer& cryproxAccessLog
    ) const {
        auto toClientOutput = MakeHttpOutput([&](TResponse&& response, const bool, TInstant deadline) {
            fromBackendTranser.NoRewind();

            backendResponse.Props().ContentLength = response.Props().ContentLength;
            backendResponse.Props().ChunkedTransfer = response.Props().ChunkedTransfer;
            for (const auto& name : FROM_CRYPROX_TO_CLIENT) {
                backendResponse.Headers().Replace(name, response.Headers().GetValuesMove(name));
            }

            return descr.Output->SendHead(std::move(backendResponse), backendForceClose, deadline);
        }, [&](TChunkList&& lst, TInstant deadline) {
            fromBackendTranser.NoRewind();
            Y_PROPAGATE_ERROR(descr.Output->Send(std::move(lst), deadline));
            if (lst.Empty()) {
                Y_PROPAGATE_ERROR(trailersSender.Flush(deadline));
            }
            return TError{};
        }, [&](THeaders&&, TInstant) -> TError {
            fromBackendTranser.NoRewind();
            return TError{};
        });

        TRequest request = CryproxRequest_;
        request.Props().ContentLength = backendResponse.Props().ContentLength;
        request.Props().ChunkedTransfer = !request.Props().ContentLength;
        request.Headers().Add("x-aab-partnertoken", PartnerToken_);
        request.Headers().Add("x-forwarded-proto", descr.Properties->UserConnIsSsl ? "https" : "http");
        if (decryptResult.url) {
            request.Headers().Add("x-aab-request-url", decryptResult.url);
        } else {
            request.Headers().Add("x-aab-request-url", TStringBuilder{}
                << (descr.Properties->UserConnIsSsl ? "https://" : "http://")
                << (descr.Request->Headers().GetFirstValue("host") ?: "localhost")
                << descr.Request->RequestLine().Path.AsStringBuf()
            );
        }
        if (decryptResult.seed) {
            request.Headers().Add("x-aab-cry-seed", decryptResult.seed);
        }
        if (auto header = descr.Request->Headers().GetValues("host")) {
            request.Headers().Add("x-aab-crypted-host", header);
        }
        for (const auto& name : FROM_CLIENT_TO_CRYPROX) {
            request.Headers().Replace(name, descr.Request->Headers().GetValues(name));
        }
        for (const auto& name : FROM_BACKEND_TO_CRYPROX) {
            request.Headers().Replace(name, backendResponse.Headers().GetValues(name));
        }

        TTcpConnProps tcpConnProps(
            descr.Process(),
            *descr.Properties->Parent.RemoteAddress,
            *descr.Properties->Parent.LocalAddress,
            nullptr
        );
        TConnProps connProps(tcpConnProps, descr.Properties->Start, descr.Properties->Random);
        TConnDescr newDescr(fromBackendTranser, toClientOutput, connProps);

        newDescr.ExtraAccessLog = TAccessLogOutput{&cryproxAccessLog};
        newDescr.ErrorLog = descr.ErrorLog;
        newDescr.Request = &request;

        const TExtraAccessLogEntry logEntry(newDescr, "cryprox_backend");
        TError error = CryproxBackend_->Run(newDescr);

        if (!error) {
            return;
        }

        if (RunningCont()->Cancelled()) {
            newDescr.ExtraAccessLog << " norewind cancelled";
            return;
        }

        bool rewind = true;
        TError rewindError = fromBackendTranser.TryToRewind(
            std::move(backendResponse), backendForceClose, descr.Output
        ).AssignTo(rewind);
        if (rewind) {
            newDescr.ExtraAccessLog << " rewind";
            ++tls.Stats.Rewind;
            cryproxError = std::move(rewindError);
            if (!cryproxError) {
                cryproxError = trailersSender.Flush(TInstant::Max());
            }
        } else {
            newDescr.ExtraAccessLog << " norewind";
            ++tls.Stats.UnableToRewind;
            if (fromBackendTranser.RewindBufferOverflow()) {
                ++tls.Stats.RewindBufferOverflow;
            }
            cryproxError = std::move(error);
        }
    }

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

private:
    TModuleStats SharedStats_;

    TRequest CryproxRequest_ = BuildRequest()
        .Method(EMethod::POST)
        .Path("/crypt_content")
        .Version11()
        .Header("host", "cryprox.yandex.net");

    TString PartnerToken_;
    TString SecretsFile_;
    TString DisableFile_;
    THolder<IModule> CryproxBackend_;
    THolder<IModule>& ServiceBackend_;
    THolder<IRequestMatcher> UseCryproxMatcher_;

    TSecrets StartSecrets_;

    size_t StreamingBufferSize_ = 16384;
    size_t RewindBufferSize_ = 1048576;
};

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