#include "http.h"
#include "httpparser.h"

#include <contrib/libs/picohttpparser/picohttpparser.h>

#include <util/string/strip.h>
#include <util/system/tls.h>

#include <array>

using namespace std::string_view_literals;

namespace NSrvKernel {

namespace {

using THeadersPhr = std::array<phr_header, MaxHeaderCount>;

Y_THREAD(THeadersPhr) HeadersBuffer;

void SplitURIIntoPathAndCGI(TStringBuf uri, TStringStorage& path, TStringStorage& cgi) noexcept {
    auto delimIt = Find(uri, '?');
    size_t pathLen = delimIt - uri.begin();
    path = TStringStorage(uri.SubStr(0, pathLen));
    if (pathLen < uri.Size()) {
        cgi = TStringStorage(uri.SubStr(pathLen));
    }
}

NNoexcept::TError<THttpParseError> ValidateParseResult(
    const THttpParseOptions& options, int32_t parsedByteCount, size_t headerCount, size_t minor,
    size_t bufferLength, TStringBuf messageTypeName)
{
    Y_REQUIRE(headerCount <= options.HeadersCountLimit, THttpParseError(HTTP_REQUEST_ENTITY_TOO_LARGE));
    Y_REQUIRE(parsedByteCount != -1 || headerCount != MaxHeaderCount, THttpParseError(
        TChunkList("too many headers to parse"), HTTP_BAD_REQUEST));
    Y_REQUIRE(parsedByteCount != -1, THttpParseError(
        TChunkList(TStringBuilder() << "can not parse " << messageTypeName), HTTP_BAD_REQUEST));
    Y_REQUIRE(parsedByteCount != -2,
              THttpParseError(
                  TChunkList(TStringBuilder() << "partial " << messageTypeName << " is not expected since " <<
                      "we have waited for double crlf"), HTTP_BAD_REQUEST));
    Y_REQUIRE(0 <= minor && minor <= 1,
              THttpParseError(TChunkList(TStringBuilder() << "HTTP/1." << minor << " is not supported"),
                              HTTP_BAD_REQUEST));
    Y_REQUIRE(static_cast<size_t>(parsedByteCount) == bufferLength,
              THttpParseError(
                  TChunkList(TStringBuilder() << "pico must have parsed the whole " << messageTypeName <<
                      " since we split all the rest"),
                  HTTP_BAD_REQUEST));
    return {};
}

NNoexcept::TError<THttpParseError> Parse(
    TStringBuf buffer, TRequestLine* requestLine, THeadersPhr& threadLocalHeaders, size_t& headerCount,
    const THttpParseOptions& options)
{
    const char* method = nullptr;
    size_t methodLen;
    const char* path = nullptr;
    size_t pathLen;
    int32_t minor;

    int32_t parsedByteCount = phr_parse_request(
        buffer.Data(), buffer.Size(), &method, &methodLen, &path, &pathLen, &minor,
        threadLocalHeaders.data(), &headerCount, /*last_len =*/ 0);

    if (minor != -1) {
        requestLine->SetUriParsed(/*parsed =*/ true);
    }

    if (options.ValidateParseResult) {
        Y_PROPAGATE_ERROR(ValidateParseResult(options, parsedByteCount, headerCount, minor, buffer.Size(),
                                              "request"));
    }

    TStringBuf methodStringBuf = TStringBuf(method, methodLen);
    TStringBuf pathStringBuf = TStringBuf(path, pathLen);

    bool methodParsed = false;
    if (methodStringBuf == "GET"sv) {
        requestLine->Method = EMethod::GET;
        methodParsed = true;
    } else if (methodStringBuf == "POST"sv) {
        requestLine->Method = EMethod::POST;
        methodParsed = true;
    } else {
        methodParsed = TryFromString<EMethod>(methodStringBuf, requestLine->Method) &&
                  requestLine->Method != EMethod::UNDEFINED;
    }

    Y_REQUIRE(methodParsed,
              THttpParseError(
                      TChunkList(TStringBuilder() << "unknown method: " << methodStringBuf), HTTP_BAD_REQUEST));
    SplitURIIntoPathAndCGI(pathStringBuf, requestLine->Path, requestLine->CGI);
    Y_REQUIRE(!requestLine->Path.Empty(),
              THttpParseError(TChunkList(TStringBuilder() << "expected non empty path"),  HTTP_BAD_REQUEST));

    requestLine->MinorVersion = minor;

    return {};
}

NNoexcept::TError<THttpParseError> Parse(
    TStringBuf buffer, TResponseLine* responseLine, THeadersPhr& threadLocalHeaders, size_t& headerCount,
    const THttpParseOptions& options)
{
    int32_t minor;
    int32_t status;
    const char* message = nullptr;
    size_t messageLen;

    int32_t parsedByteCount = phr_parse_response(
        buffer.Data(), buffer.Size(), &minor, &status, &message, &messageLen,
        threadLocalHeaders.data(), &headerCount, /*last_len =*/ 0);

    if (options.ValidateParseResult) {
        Y_PROPAGATE_ERROR(ValidateParseResult(options, parsedByteCount, headerCount, minor, buffer.Size(),
                                              "response"));
    }

    Y_REQUIRE(100 <= status && status <= 599,
              THttpParseError(HTTP_BAD_REQUEST) << "bad response status: " << status);

    responseLine->MinorVersion = static_cast<ui8>(minor);
    responseLine->StatusCode = static_cast<ui16>(status);
    responseLine->Reason = TStringStorage(TStringBuf(message, message + messageLen));

    return {};
}

}  // namespace

template <typename TMessage>
TParseMessagePicoContext<TMessage>::TParseMessagePicoContext(
    TChunkList* unparsed, TString* buffer, TMessage* message, THttpParseOptions options
) noexcept
    : NHttpParserInternals::TParseContext<TParseMessagePicoContext<TMessage>>(unparsed, buffer)
    , Message_(message)
    , HeadLine_(&message->HeadLine())
    , Properties_(&message->Props())
    , Headers_(&message->Headers())
    , ParseOptions_(std::move(options))
{}

template <typename TMessage>
TErrorOr<bool> TParseMessagePicoContext<TMessage>::DoParseChunk(TString buffer) noexcept {
    size_t headerCount = MaxHeaderCount;
    auto& threadLocalHeaders = HeadersBuffer.Get();

    NNoexcept::TError<THttpParseError> error = Parse(buffer, HeadLine_, threadLocalHeaders, headerCount,
                                                     ParseOptions_);
    if (error) {
        TString errorHead = TStringBuilder() << "\n" << MessageTypeName() << " head:\n";
        errorHead += buffer;
        error.template GetAs<THttpParseError>()->AppendChunks(TChunkList(errorHead));
        return error;
    }

    Properties_->SetReasonableDefaults(Message_);

    bool hostFound = false;

    Headers_->Clear(std::move(buffer));

    if (ParseOptions_.FillHeadersMap) {
        Headers_->reserve(headerCount);
    }

    for (size_t i = 0; i < headerCount; i++) {
        TStringBuf key = TStringBuf(threadLocalHeaders[i].name, threadLocalHeaders[i].name_len);
        TStringBuf value = TStringBuf(threadLocalHeaders[i].value, threadLocalHeaders[i].value_len);

        Y_REQUIRE(!key.Empty(), THttpParseError(TChunkList("empty header key"), HTTP_BAD_REQUEST));

        bool addHeader = false;

        if (Y_UNLIKELY(AsciiEqualsIgnoreCase(key, "transfer-encoding"))) {
            if (AsciiEqualsIgnoreCase(value, "chunked")) {
                Properties_->ChunkedTransfer = true;
            }
        } else if (Y_UNLIKELY(AsciiEqualsIgnoreCase(key, "content-length"))) {
            Properties_->ContentLength.emplace();
            Y_REQUIRE(TryFromString<ui64>(value, *Properties_->ContentLength),
                      THttpParseError(TChunkList(TStringBuilder() << "couldn't parse content-length: " << value),
                                      HTTP_BAD_REQUEST));
        } else if (Y_UNLIKELY(AsciiEqualsIgnoreCase(key, "connection"))) {
            for (const auto& singleValueToken: StringSplitter(value).Split(',')) {
                TStringBuf singleValue = StripString(singleValueToken.Token());
                if (AsciiEqualsIgnoreCase(singleValue, "keep-alive")) {
                    Properties_->KeepAlive = true;
                    Properties_->ExplicitKeepAliveHeader = true;
                    Properties_->ExplicitConnectionHeader = true;
                } else if (AsciiEqualsIgnoreCase(singleValue, "close")) {
                    Properties_->KeepAlive = false;
                    Properties_->ExplicitConnectionHeader = true;
                } else if (AsciiEqualsIgnoreCase(singleValue, "upgrade")) {
                    Properties_->UpgradeRequested = true;
                    Properties_->ExplicitConnectionHeader = true;
                }
            }
        } else if (Y_UNLIKELY(AsciiEqualsIgnoreCase(key, "host"))) {
            if (Y_LIKELY(!ParseOptions_.MultipleHostsEnabled)) {
                // https://tools.ietf.org/html/rfc7230#section-5.4
                Y_REQUIRE(!hostFound, THttpParseError(HTTP_BAD_REQUEST));
                hostFound = true;
            }
            addHeader = true;
        } else if (Y_UNLIKELY(AsciiEqualsIgnoreCase(key, "server"))) {
            ;
        } else {
            addHeader = true;
        }

        if (ParseOptions_.FillHeadersMap && (addHeader || ParseOptions_.KeepAllHeaders)) {
            Headers_->AddFromParser(key, value);
        }
    }

    return true;
}

template <>
TError TParseMessagePicoContext<TRequest>::DoOnIncompleteInput() noexcept {
    if (this->Buffer_->Empty()) {
        return Y_MAKE_ERROR(THttpParseIncompleteInputError(TChunkList("empty request line"), HTTP_BAD_REQUEST));
    }
    auto maybeError = ParseChunk();
    Y_REQUIRE(maybeError, THttpParseError(
        TChunkList("error expected during incomplete input parsing"), HTTP_BAD_REQUEST));
    ui16 requestCode = HTTP_BAD_REQUEST;
    auto error = maybeError.ReleaseError();
    if (auto* httpParseError = error.GetAs<THttpParseError>()) {
        requestCode = httpParseError->Code();
    }
    return Y_MAKE_ERROR(THttpParseIncompleteInputError(TChunkList("incomplete request input"), requestCode));
}

template <>
TError TParseMessagePicoContext<TResponse>::DoOnIncompleteInput() noexcept {
    if (this->Buffer_->Empty()) {
        return Y_MAKE_ERROR(THttpParseIncompleteInputError(TChunkList("empty response line"), HTTP_BAD_REQUEST));
    }
    return Y_MAKE_ERROR(THttpParseIncompleteInputError(TChunkList("incomplete response input"), HTTP_BAD_REQUEST));
}

template <typename TMessage>
constexpr TStringBuf TParseMessagePicoContext<TMessage>::MessageTypeName() const noexcept {
    if constexpr (std::is_same_v<TMessage, TRequest>) {
        return "request";
    }
    return "response";
}

template class TParseMessagePicoContext<TRequest>;
template class TParseMessagePicoContext<TResponse>;

}  // namespace NSrvKernel
