#include "http2_headers.h"
#include "http2_settings.h"

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

#include <balancer/kernel/http/parser/common_headers.h>
#include <balancer/kernel/memory/strip.h>
#include <balancer/kernel/regexp/regexp_pire.h>

#include <util/generic/fwd.h>
#include <util/generic/strbuf.h>
#include <util/string/strip.h>
#include <util/string/util.h>
#include <util/string/builder.h>

namespace NSrvKernel::NHTTP2 {
    namespace {
        auto HeaderError(EStreamProtocolError reason) {
            return TStreamError(EErrorCode::PROTOCOL_ERROR, reason);
        };

        bool IsPathValid(const TChunkList& value) noexcept {
            TUnitedChunkList path{value};
            return path && IsIn<char>({'/', '*'}, path.AsStringBuf()[0]);
        }

        [[nodiscard]]
        TError DoGetSpecHeader(
            TMaybe<TChunkList>& headerValue, const TStringBuf headerName, THeader& header) noexcept
        {
            if (headerName == header.Key) {
                Y_REQUIRE(!headerValue,
                    HeaderError(EStreamProtocolError::DuplicateHeader));
                headerValue = std::move(header.Value);
            }
            return {};
        }

        [[nodiscard]]
        TErrorOr<ui32> DoParseContentLength(const TChunkList& value) noexcept {
            TUnitedChunkList lst{value};
            ui32 len = 0;
            Y_REQUIRE(TryFromString<ui32>(lst.AsStringBuf(), len),
                HeaderError(EStreamProtocolError::BadContentLength));
            return len;
        }

        void DoSetRequestDefaults(TBaseProperties& props) noexcept {
            props.Version = 1;
            props.ChunkedTransfer = false;
            props.ContentLength.reset();
            props.ExplicitConnectionHeader = false;
            props.ExplicitKeepAliveHeader = false;
            props.KeepAlive = false;
            props.Reused = false;
        }

        [[nodiscard]]
        TError DoInitRequestLine(
            THTTP11Request& request, TMaybe<TChunkList>& method, TMaybe<TChunkList>& path) noexcept
        {
            Y_REQUIRE(method,
                HeaderError(EStreamProtocolError::NoMethod));

            Y_REQUIRE(path,
                HeaderError(EStreamProtocolError::NoPath));

            Y_REQUIRE(IsPathValid(*path),
                HeaderError(EStreamProtocolError::BadPath));
            {
                TString reqLine;
                reqLine += StrInplace(*method);
                reqLine += SP;
                reqLine += StrInplace(*path);
                reqLine += " HTTP/1.1\r\n\r\n";

                // Will validate the method and path for us.
                TRequest tmp;
                Y_PROPAGATE_ERROR(tmp.Parse(std::move(reqLine)));

                request.RequestLine().Swap(tmp.RequestLine());
                return {};
            }
        }
    }

    void THTTP11Request::PrintTo(IOutputStream& out, bool onlySpec) const {
        RequestLine().PrintNoCRLF(out);
        out << "[CRLF]";
        out << THeadersPrinter(Headers(), onlySpec, false);
    }

    TString PrintReqSpecHeaders(const THeadersList& headers) noexcept {
        return TStringBuilder() << THeadersPrinter(headers, true, false);
    }

    TString PrintReqAllHeaders(const THeadersList& headers) noexcept {
        return TStringBuilder() << THeadersPrinter(headers, false, false);
    }

    TString PrintRespSpecHeaders(const THeadersList& headers) noexcept {
        return TStringBuilder() << THeadersPrinter(headers, true, true);
    }

    TString PrintRespAllHeaders(const THeadersList& headers) noexcept {
        return TStringBuilder() << THeadersPrinter(headers, false, true);
    }

    TString PrintReqSpecHeaders(const THTTP11Request& headers) noexcept {
        TStringBuilder str;
        headers.PrintTo(str.Out, true);
        return str;
    }

    TString PrintReqAllHeaders(const THTTP11Request& headers) noexcept {
        TStringBuilder str;
        headers.PrintTo(str.Out, false);
        return str;
    }

    TErrorOr<THTTP11Request> ConvertRequest2To11(TLogger& logger, TStats& stats, THeadersList headers, const bool endOfStream, bool allowTEHeader) noexcept {
        bool inRequestLine = true;

        THTTP11Request request;

        DoSetRequestDefaults(request.Props());

        TMaybe<TChunkList> method;
        TMaybe<TChunkList> scheme;
        TMaybe<TChunkList> path;
        TMaybe<TChunkList> authority;
        TMaybe<TChunkList> cookie;

        bool hasHost = false;
        bool headerDropped = false;

        auto it = headers.begin();
        while (it != headers.end()) {
            auto& header = *it;

            Y_REQUIRE(header.Key.size(),
                HeaderError(EStreamProtocolError::EmptyHeaderName));

            if (!IsRequestHeaderValueValid(header.Value)) {
                Y_HTTP2_EVENT(logger, "Drop req header", EscC(header));
                headerDropped = true;
                ++it;
                continue;
            }

            if (IsRequestHeaderSpecial(header.Key)) {
                Y_REQUIRE(inRequestLine,
                    HeaderError(EStreamProtocolError::MisplacedSpecHeader) << header.Key);

                Y_PROPAGATE_ERROR(DoGetSpecHeader(method, TStringBuf(REQ_HEADER_METHOD), header));
                Y_PROPAGATE_ERROR(DoGetSpecHeader(scheme, TStringBuf(REQ_HEADER_SCHEME), header));
                Y_PROPAGATE_ERROR(DoGetSpecHeader(path, TStringBuf(REQ_HEADER_PATH), header));
                Y_PROPAGATE_ERROR(DoGetSpecHeader(authority, TStringBuf(REQ_HEADER_AUTHORITY), header));
            } else {
                // RFC 7540: A request or response containing uppercase
                //           header field names MUST be treated as malformed

                // BALANCER-1518, BALANCER-1972 Skipping invalid headers.
                if (!IsRequestHeaderNameValid(header.Key)) {
                    Y_HTTP2_EVENT(logger, "Drop req header", EscC(header));
                    headerDropped = true;
                    ++it;
                    continue;
                }

                inRequestLine = false;

                if (TStringBuf(REQ_HEADER_COOKIE) == header.Key) {
                    // RFC 7540: If there are multiple Cookie header fields after
                    //           decompression, these MUST be concatenated into a single octet string
                    //           using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ")
                    //           before being passed into a non-HTTP/2 context, such as an HTTP/1.1
                    //           connection, or a generic HTTP server application.
                    if (cookie && cookie->ChunksCount()) {
                        cookie->Push("; ");
                        cookie->Append(std::move(header.Value));
                    } else {
                        cookie = std::move(header.Value);
                    }
                } else if (TStringBuf(REQ_HEADER_HOST) == header.Key) {
                    // RFC 7230: A server MUST respond with a 400 (Bad Request) status code to any
                    //           HTTP/1.1 request message that lacks a Host header field and to any
                    //           request message that contains more than one Host header field or a
                    //           Host header field with an invalid field-value.
                    Y_REQUIRE(!hasHost,
                        HeaderError(EStreamProtocolError::DuplicateHost));

                    if (header.Value.size()) {
                        hasHost = true;
                        // Properly relinks the list item to the new list.
                        request.Headers().Add(TString(StrInplace(it->Key)), TString(StrInplace(it->Value)));
                        headers.erase(it++);
                        continue;
                    }
                } else if (TStringBuf(HEADER_CONTENT_LENGTH) == header.Key) {
                    Y_REQUIRE(!request.Props().ContentLength,
                        HeaderError(EStreamProtocolError::BadContentLength) << EscC(header.Value));

                    Y_PROPAGATE_ERROR(
                        DoParseContentLength(header.Value).AssignTo(request.Props().ContentLength));
                } else {
                    // TODO (velavokr): implement trailers properly
                    Y_REQUIRE(TStringBuf(HEADER_TRAILER) != header.Key,
                        TStreamError(EErrorCode::INTERNAL_ERROR, EStreamInternalError::TrailersNotImplemented));

                    // RFC 7540: The only exception to this is the TE header field, which MAY be
                    //           present in an HTTP/2 request; when it is, it MUST NOT contain any
                    //           value other than "trailers".
                    if (TStringBuf(REQ_HEADER_TE) == header.Key) {
                        Y_REQUIRE(TStringBuf(REQ_HEADER_TE_TRAILERS) == header.Value,
                            HeaderError(EStreamProtocolError::ProhibitedHeaderValue) << EscC(header.Value));

                        if (allowTEHeader) {
                            // Properly relinks the list item to the new list.
                            request.Headers().Add(TString(StrInplace((it->Key))), TString(StrInplace(it->Value)));
                            headers.erase(it++);
                        } else {
                            headerDropped = true;
                            it++;
                        }
                        continue;
                    }

                    // RFC 7540: An endpoint MUST NOT
                    //           generate an HTTP/2 message containing connection-specific header
                    //           fields; any message containing connection-specific header fields MUST
                    //           be treated as malformed
                    Y_REQUIRE(!IsHeaderProhibited(header.Key),
                        HeaderError(EStreamProtocolError::ProhibitedHeader) << EscC(header));

                    // Properly relinks the list item to the new list.
                    request.Headers().Add(TString(StrInplace((it->Key))), TString(StrInplace(it->Value)));
                    headers.erase(it++);
                    continue;
                }
            }
            ++it;
        }

        if (headerDropped) {
            stats.ReqsWithHeadersDropped += 1;
        }

        // BALANCER-1311, SPINCIDENTS-1281
        // Forwarding GET or HEAD with an empty chunked body may trigger bugs in backends.
        if (!request.Props().ContentLength && endOfStream) {
            request.Props().ContentLength = 0;
        }

        Y_PROPAGATE_ERROR(DoInitRequestLine(request, method, path));

        Y_REQUIRE(scheme,
            HeaderError(EStreamProtocolError::NoScheme));

        request.Scheme = std::move(*scheme);

        // RFC 7540: An intermediary that converts an HTTP/2 request to HTTP/1.1 MUST
        //           create a Host header field if one is not present in a request by
        //           copying the value of the ":authority" pseudo-header field.
        if (!hasHost) {
            Y_REQUIRE(authority,
                HeaderError(EStreamProtocolError::NoHost));
            request.Headers().Add(REQ_HEADER_HOST, TString(StrInplace(*authority)));
        }

        if (cookie) {
            request.Headers().Add(REQ_HEADER_COOKIE, TString(StrInplace(*cookie)));
        }

        return std::move(request);
    }


    THeadersList ConvertResponse11To2(TLogger& logger, TStats& stats, TResponse response) noexcept {
        THeadersList headers;

        headers.PushBack(new THeader(
            TChunkList(RESP_HEADER_STATUS),
            TChunkList(::ToString(response.ResponseLine().StatusCode))
        ));

        // We expect the THttpDecoder to delete the Content-Length header
        if (response.Props().ContentLength) {
            headers.PushBack(new THeader(
                TChunkList(HEADER_CONTENT_LENGTH),
                TChunkList(::ToString(*response.Props().ContentLength))
            ));
        }

        bool headerDropped = false;
        EraseNodesIf(response.Headers(), [&](auto& header) {
            // BALANCER-1268 Prevent chunked in server response
            Y_VERIFY(!IsTransferEncoding(header.first.AsStringBuf()));
            // RFC 7540: an intermediary transforming an HTTP/1.x message to
            //           HTTP/2 will need to remove any header fields nominated by the
            //           Connection header field, along with the Connection header field
            //           itself.  Such intermediaries SHOULD also remove other connection-
            //           specific header fields, such as Keep-Alive, Proxy-Connection,
            //           Transfer-Encoding, and Upgrade, even if they are not nominated by the
            //           Connection header field.
            if (IsResponseHeaderNameValid(header.first.AsStringBuf())
                && !IsHeaderProhibited(header.first.AsStringBuf()))
            {
                // Properly relinks the list item to the new list
                for (auto&& value : header.second) {
                    headers.Add(TChunkList{TString(header.first.AsStringBuf())},
                                      TChunkList{TString(value.AsStringBuf())});
                }
                return true;
            } else {
                Y_HTTP2_EVENT(logger, "Drop resp header",
                              EscC(THeader{TChunkList{TString(header.first.AsStringBuf())},
                                           TChunkList{TString(header.second[0].AsStringBuf())}}));
                headerDropped = true;
                return false;
            }
        });

        if (headerDropped) {
            stats.RespsWithHeadersDropped += 1;
        }

        return headers;
    }

    bool ContainsSpecHeaders(const THeadersList& headers) noexcept {
        return headers && IsRequestHeaderSpecial(headers.Front()->Key);
    }

    bool Is1xxInformational(const TResponse& response) noexcept {
        return response.ResponseLine().StatusCode / 100 == 1;
    }

    EErrorCode GetStreamErrorCode(HttpCodes errorCode) noexcept {
        if (400 <= (int)errorCode && (int)errorCode < 500) {
            return EErrorCode::PROTOCOL_ERROR;
        } else {
            return EErrorCode::INTERNAL_ERROR;
        }
    }

    TStreamErrorReason GetStreamErrorReason(HttpCodes errorCode) noexcept {
        if (400 <= (int)errorCode && (int)errorCode < 500) {
            return EStreamProtocolError::Http4xx;
        } else if (500 <= (int)errorCode && (int)errorCode < 600) {
            return EStreamInternalError::BackendError;
        } else {
            return EStreamInternalError::UnknownError;
        }
    }

    ui64 size(const THeadersList& headers) noexcept {
        ui64 res = 0;

        for (const auto& header : headers) {
            res += header.Key.size() + header.Value.size();
        }

        return res;
    }
}

Y_HTTP2_GEN_PRINT(THTTP11Request);
Y_HTTP2_GEN_PRINT(THeadersPrinter<NSrvKernel::THeaders>);
Y_HTTP2_GEN_PRINT(THeadersPrinter<NSrvKernel::NHTTP2::THeadersList>);
