#include "module.h"

#include <balancer/kernel/http2/server/http2.h>
#include <balancer/kernel/http2/server/common/http2_common.h>

#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/fs/shared_file_rereader.h>
#include <balancer/kernel/fs/watched_state.h>
#include <balancer/kernel/log/logbackend.h>
#include <balancer/kernel/module/iface.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/process/thread_info.h>

#include <util/generic/vector.h>
#include <util/stream/output.h>
#include <util/string/strip.h>
#include <util/thread/singleton.h>
#include <util/generic/xrange.h>

namespace NSrvKernel::NHTTP2 {
    struct TDebugLogOptions {
        TString Name;

        TString File;
        double Default = 1.0;

        ELogPriority Level = TLOG_DEBUG;
        bool Enabled = false;
    };

    class TDebugLog {
    public:
        TDebugLog(TDebugLogOptions options, IWorkerCtl* worker)
            : Options_(std::move(options))
            , Freq_(Options_.Default, Options_.File, *worker->SharedFiles())
        {
            if (Options_.Enabled) {
                Log_.Reset(new TLog(MakeHolder<TMetaLogBackend>(worker->GetLog(Options_.Name))));
                Log_->SetDefaultPriority(Options_.Level);
            }
        }

        TLog* Get() const {
            return Options_.Enabled && RandomNumber<double>() < Freq_.Get() ? Log_.Get() : nullptr;
        }
    private:
        THolder<TLog> Log_;
        TDebugLogOptions Options_;
        TWatchedState<double> Freq_ = 1.0;
    };
}

using namespace NSrvKernel;

Y_TLS(http2) {
    TTls(const NHTTP2::TStats& stats, NHTTP2::TDebugLogOptions logOptions, IWorkerCtl* worker)
        : WorkerStats(stats, worker->WorkerId())
        , WorkerDebugLog(std::move(logOptions), worker)
    {}

    bool DoesRefusedStreamKillSwitchExist() const {
        return RefusedStreamKillSwitch.Exists();
    }

    NHTTP2::TStats WorkerStats;
    TSharedFileExistsChecker RefusedStreamKillSwitch;
    NHTTP2::TDebugLog WorkerDebugLog;
};

MODULE_WITH_TLS(http2), public NHTTP2::IHTTP2Module {
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
        , NHTTP2::IHTTP2Module()
        , Stats_(mp.Control->SharedStatsManager())
    {
        Config->ForEach(this);

        Y_ENSURE_EX(AuxServerSettings_.ServerFrameSizeMax >= AuxServerSettings_.ServerDataFrameSizeMax,
                    TConfigParseError() << "server_frame_size_max < server_data_frame_size_max");

        Y_ENSURE_EX(AuxServerSettings_.StreamSendBufferLoMax >= AuxServerSettings_.ServerDataFrameSizeMax,
                    TConfigParseError() << "stream_send_buffer_lo_max < server_data_frame_size_max");
        Y_ENSURE_EX(AuxServerSettings_.StreamSendBufferHiMax >= AuxServerSettings_.StreamSendBufferLoMax,
                    TConfigParseError() << "stream_send_buffer_hi_max < stream_send_buffer_lo_max");

        Y_ENSURE_EX(AuxServerSettings_.ConnDataSendBufferMax >= AuxServerSettings_.ServerFrameSizeMax,
                    TConfigParseError() << "conn_send_buffer_max < server_frame_size_max");
        Y_ENSURE_EX(AuxServerSettings_.ConnCtrlSendBufferMax >= AuxServerSettings_.ServerFrameSizeMax,
                    TConfigParseError() << "ctrl_send_queue_size_max < server_frame_size_max");

        Y_ENSURE_EX(Submodule_,
                    TConfigParseError() << "No child module configured");

        if (DebugLogOptions_.Enabled) {
            mp.Control->CreateLog(DebugLogOptions_.Name);
        }
    }

private:
    START_PARSE {
        PARSE_EVENTS;

        // The upper bound of the client RTT estimation (default - 60s).
        ON_KEY("client_rtt_estimate_max", AuxServerSettings_.ClientRTTEstimateMax) { return; }
        // We do not send frames bigger than this. A malicious client might trick us into doing so.
        ON_KEY("server_frame_size_max", AuxServerSettings_.ServerFrameSizeMax) {
            Y_ENSURE_EX(AuxServerSettings_.ServerFrameSizeMax >= NHTTP2::IMPL_FRAME_SIZE_MAX_MIN,
                        TConfigParseError() << "server_frame_size_max < " << NHTTP2::IMPL_FRAME_SIZE_MAX_MIN);
            return;
        }
        // We do not send data frames bigger than this.
        ON_KEY("server_data_frame_size_max", AuxServerSettings_.ServerDataFrameSizeMax) {
            Y_ENSURE_EX(AuxServerSettings_.ServerDataFrameSizeMax >= NHTTP2::IMPL_DATA_FRAME_SIZE_MAX_MIN,
                        TConfigParseError() << "server_data_frame_size_max < " << NHTTP2::IMPL_DATA_FRAME_SIZE_MAX_MIN);
            return;
        }

        // A malicious client might send too many headers to force us into consuming extra memory.
        ON_KEY("client_headers_count_max", AuxServerSettings_.ClientHeadersCountMax) { return; }
        // We do not want to use too big dynamic table for encoding. A malicious client might trick us into doing so.
        ON_KEY("encoder_table_size_max", AuxServerSettings_.EncoderTableSizeMax) { return; }

        // See BALANCER-1352 for details
        // A lower bound for stream send buffer. Must be >= server_data_frame_size_max.
        ON_KEY("stream_send_buffer_lo_max", AuxServerSettings_.StreamSendBufferLoMax) { return; }
        // An upper bound for stream send buffer. Must be >= stream_send_buffer_lo_max.
        ON_KEY("stream_send_buffer_hi_max", AuxServerSettings_.StreamSendBufferHiMax) { return; }
        // How slow the stream send buffer size decays with increase of the number of open streams.
        ON_KEY("stream_send_buffer_load_step", AuxServerSettings_.StreamSendBufferLoadStep) {
            Y_ENSURE_EX(AuxServerSettings_.StreamSendBufferLoadStep > 0,
                        TConfigParseError() << "stream_send_buffer_load_step == 0");
            return;
        }

        // How much data can be buffered for sending before the sending blocks. Must be >= server_frame_size_max.
        ON_KEY("conn_data_send_buffer_max", AuxServerSettings_.ConnDataSendBufferMax) { return; }
        // Reserved space for control traffic in the sending queue. Must be >= server_frame_size_max.
        ON_KEY("conn_ctrl_send_buffer_max", AuxServerSettings_.ConnCtrlSendBufferMax) { return; }

        // How many idle streams we should keep track of to properly handle priorities.
        ON_KEY("streams_idle_max", AuxServerSettings_.StreamsIdleMax) { return; }
        // How many closed streams we should keep track of to properly handle priorities.
        ON_KEY("streams_closed_max", AuxServerSettings_.StreamsClosedMax) { return; }

        // The queue type for streams prioritization.
        ON_KEY("streams_prio_queue_type", AuxServerSettings_.StreamsPrioQueueType) { return; }
        // A replacement for MS Edge unimplemented priorities. Enabled by default.
        ON_KEY("edge_prio_fix_enabled", AuxServerSettings_.EdgePrioFixEnabled) { return; }

        // In testing it might be useful to enable the GOAWAY debug payload.
        ON_KEY("goaway_debug_data_enabled", AuxServerSettings_.GoAwayDebugDataEnabled) { return; }

        // RTT estimation with reciprocal pings before ping-acks.
        ON_KEY("reciprocal_ping_enabled", AuxServerSettings_.ReciprocalPingEnabled) { return; }
        // RTT estimation during flushes.
        ON_KEY("flush_ping_enabled", AuxServerSettings_.FlushPingEnabled) { return; }

        // The in-balancer socket send buffer limit. Reducing it may improve prioritization.
        ON_KEY("sock_send_buffer_size_max", AuxServerSettings_.SockSendBufferSizeMax) { return; }

        ON_KEY("allow_http2_without_ssl", AuxServerSettings_.AllowHttp2WithoutSsl) { return; }

        ON_KEY("allow_sending_trailers", AuxServerSettings_.AllowSendingTrailers) { return; }

        // A way to disable generating RST_STREAM=REFUSED_STREAM on errors when no response was sent to a client
        ON_KEY("refused_stream_file_switch", RefusedStreamKillSwitchFile_) { return; }

        // The http2 module may have its private logging.
        ON_KEY("debug_log_enabled", DebugLogOptions_.Enabled) { return; }
        // The log name for the http2 private logging, empty for stderr.
        ON_KEY("debug_log_name", DebugLogOptions_.Name) { return; }
        // The log level for the http2 private logging, DEBUG by default.
        ON_KEY("debug_log_level", DebugLogOptions_.Level) { return; }
        // The default frequency http2 module may have its private logging.
        ON_KEY("debug_log_freq", DebugLogOptions_.Default) { return; }
        // The debug log frequency switch
        ON_KEY("debug_log_freq_file", DebugLogOptions_.File) { return; }

        // RFC 7540: SETTINGS_HEADER_TABLE_SIZE (0x1):  Allows the sender to inform the
        //           remote endpoint of the maximum size of the header compression
        //           table used to decode header blocks, in octets.  The encoder can
        //           select any size equal to or less than this value by using
        //           signaling specific to the header compression format inside a
        //           header block (see [COMPRESSION]).  The initial value is 4,096
        //           octets.
        ON_KEY("rfc_header_table_size", ServerSettings_.HeaderTableSize) { return; }

        // RFC 7540: SETTINGS_MAX_CONCURRENT_STREAMS (0x3):  Indicates the maximum number
        //           of concurrent streams that the sender will allow.  This limit is
        //           directional: it applies to the number of streams that the sender
        //           permits the receiver to create.  Initially, there is no limit to
        //           this value.  It is recommended that this value be no smaller than
        //           100, so as to not unnecessarily limit parallelism.
        // Impl: See NHTTP2::GetDefaultServerSettings() for our actual default on this.
        ON_KEY("rfc_max_concurrent_streams", ServerSettings_.MaxConcurrentStreams) { return; }

        // RFC 7540: SETTINGS_INITIAL_WINDOW_SIZE (0x4):  Indicates the sender's initial
        //           window size (in octets) for stream-level flow control.  The
        //           initial value is 2^16-1 (65,535) octets.
        //           This setting affects the window size of all streams
        ON_KEY("rfc_initial_window_size", ServerSettings_.InitialWindowSize) { return; }

        // RFC 7540: SETTINGS_MAX_FRAME_SIZE (0x5):  Indicates the size of the largest
        //           frame payload that the sender is willing to receive, in octets.
        //           The initial value is 2^14 (16,384) octets.  The value advertised
        //           by an endpoint MUST be between this initial value and the maximum
        //           allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
        ON_KEY("rfc_max_frame_size", ServerSettings_.MaxFrameSize) {
            Y_ENSURE(ServerSettings_.MaxFrameSize >= NHTTP2::RFC_MAX_FRAME_SIZE_MIN
                     && ServerSettings_.MaxFrameSize <= NHTTP2::RFC_MAX_FRAME_SIZE_MAX);
            return;
        }

        // RFC 7540: SETTINGS_MAX_HEADER_LIST_SIZE (0x6):  This advisory setting informs a
        //           peer of the maximum size of header list that the sender is
        //           prepared to accept, in octets.  The value is based on the
        //           uncompressed size of header fields, including the length of the
        //           name and value in octets plus an overhead of 32 octets for each
        //           header field.
        // Impl: See NHTTP2::GetDefaultServerSettings() for our actual default on this.
        ON_KEY("rfc_max_header_list_size", ServerSettings_.MaxHeaderListSize) { return; }

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

    THolder<TTls> DoInitTls(IWorkerCtl* worker) override {
        auto tls = MakeHolder<TTls>(Stats_, DebugLogOptions_, worker);
        if (RefusedStreamKillSwitchFile_) {
            tls->RefusedStreamKillSwitch = worker->SharedFiles()->FileChecker(
                    RefusedStreamKillSwitchFile_,
                    TDuration::Seconds(1)
            );
        }
        return tls;
    }

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

    void DoCheckConstraints() const override {
        // TODO(velavokr): the module graph validation needs reengineering. BALANCER-1818
        static const TSet<TString> allowedParents {
            "errorlog",
            "ipdispatch",
            "regexp",
            "report",
            "ssl_sni",
        };
        // TODO(ialand or velavokr): fix checking

        CheckParents([&](TStringBuf name) {
            Y_ENSURE_EX(name != "http2",
                TConfigParseError() << name << " is not allowed as a parent of http2. Only "
                                    << JoinStrings(allowedParents.begin(), allowedParents.end(), ", ") << " are.";
            );
            return false;
        });
    }

    TError DoRun(const TConnDescr& descr, TTls& tls) const noexcept override {
        auto auxServerSettings = AuxServerSettings_;
        if (tls.DoesRefusedStreamKillSwitchExist()) {
            auxServerSettings.RefusedStreamOnErrorEnabled = false;
        }
        return OnConnection(descr, tls.WorkerDebugLog.Get(), ServerSettings_, auxServerSettings);
    }

    const TModuleParams& GetModuleParams() const noexcept override {
        return *this;
    }

    const IModule& GetNextModule() const noexcept override {
        return *Submodule_;
    }

    NHTTP2::TStats& GetStats(const TConnDescr& descr) const noexcept override {
        return GetTls(&descr.Process()).WorkerStats;
    }

    TStringBuf GetModuleName() const noexcept override {
        return NAME;
    }

private:
    NHTTP2::TStats Stats_;

    NHTTP2::TSettings ServerSettings_ = NHTTP2::GetDefaultServerSettings();
    NHTTP2::TAuxServerSettings AuxServerSettings_;
    TString RefusedStreamKillSwitchFile_;
    THolder<IModule> Submodule_;

    NHTTP2::TDebugLogOptions DebugLogOptions_;
};


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