/*************************************************************************
 *
 * Copyright 2022 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************/

#pragma once

#include <cstdint>
#include <cstdlib>
#include <type_traits>
#include <map>
#include <system_error>
#include <iosfwd>
#include <locale>
#include <sstream>

#include <realm/util/optional.hpp>
#include <realm/util/basic_system_errors.hpp>
#include <realm/util/logger.hpp>
#include <realm/string_data.hpp>

namespace realm::sync {
enum class HTTPParserError {
    None = 0,
    ContentTooLong,
    HeaderLineTooLong,
    MalformedResponse,
    MalformedRequest,
    BadRequest,
};
std::error_code make_error_code(HTTPParserError);
} // namespace realm::sync

namespace std {
template <>
struct is_error_code_enum<realm::sync::HTTPParserError> : std::true_type {};
} // namespace std

namespace realm::sync {

/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
///
/// It is guaranteed that the backing integer value of this enum corresponds
/// to the numerical code representing the status.
enum class HTTPStatus {
    Unknown = 0,

    Continue = 100,
    SwitchingProtocols = 101,

    Ok = 200,
    Created = 201,
    Accepted = 202,
    NonAuthoritative = 203,
    NoContent = 204,
    ResetContent = 205,
    PartialContent = 206,

    MultipleChoices = 300,
    MovedPermanently = 301,
    Found = 302,
    SeeOther = 303,
    NotModified = 304,
    UseProxy = 305,
    SwitchProxy = 306,
    TemporaryRedirect = 307,
    PermanentRedirect = 308,

    BadRequest = 400,
    Unauthorized = 401,
    PaymentRequired = 402,
    Forbidden = 403,
    NotFound = 404,
    MethodNotAllowed = 405,
    NotAcceptable = 406,
    ProxyAuthenticationRequired = 407,
    RequestTimeout = 408,
    Conflict = 409,
    Gone = 410,
    LengthRequired = 411,
    PreconditionFailed = 412,
    PayloadTooLarge = 413,
    UriTooLong = 414,
    UnsupportedMediaType = 415,
    RangeNotSatisfiable = 416,
    ExpectationFailed = 417,
    ImATeapot = 418,
    MisdirectedRequest = 421,
    UpgradeRequired = 426,
    PreconditionRequired = 428,
    TooManyRequests = 429,
    RequestHeaderFieldsTooLarge = 431,
    UnavailableForLegalReasons = 451,

    InternalServerError = 500,
    NotImplemented = 501,
    BadGateway = 502,
    ServiceUnavailable = 503,
    GatewayTimeout = 504,
    HttpVersionNotSupported = 505,
    VariantAlsoNegotiates = 506,
    NotExtended = 510,
    NetworkAuthenticationRequired = 511,
};

bool valid_http_status_code(unsigned int code);

/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
enum class HTTPMethod {
    Options,
    Get,
    Head,
    Post,
    Put,
    Patch,
    Delete,
    Trace,
    Connect,
};

struct HTTPAuthorization {
    std::string scheme;
    std::map<std::string, std::string> values;
};

HTTPAuthorization parse_authorization(const std::string&);

class HeterogeneousCaseInsensitiveCompare {
public:
    using is_transparent = std::true_type;
    template <class A, class B>
    bool operator()(const A& a, const B& b) const noexcept
    {
        return comp(std::string_view(a), std::string_view(b));
    }

private:
    bool comp(std::string_view a, std::string_view b) const noexcept
    {
        auto cmp = [](char lhs, char rhs) {
            return std::tolower(lhs, std::locale::classic()) < std::tolower(rhs, std::locale::classic());
        };
        return std::lexicographical_compare(begin(a), end(a), begin(b), end(b), cmp);
    }
};

/// Case-insensitive map suitable for storing HTTP headers.
using HTTPHeaders = std::map<std::string, std::string, HeterogeneousCaseInsensitiveCompare>;

struct HTTPRequest {
    HTTPMethod method = HTTPMethod::Get;
    HTTPHeaders headers;
    std::string path;

    /// If the request object has a body, the Content-Length header MUST be
    /// set to a string representation of the number of bytes in the body.
    /// FIXME: Relax this restriction, and also support Transfer-Encoding
    /// and other HTTP/1.1 features.
    util::Optional<std::string> body;
};

struct HTTPResponse {
    HTTPStatus status = HTTPStatus::Unknown;
    std::string reason;
    HTTPHeaders headers;

    // A body is only read from the response stream if the server sent the
    // Content-Length header.
    // FIXME: Support other transfer methods, including Transfer-Encoding and
    // HTTP/1.1 features.
    util::Optional<std::string> body;
};


/// Serialize HTTP request to output stream.
std::ostream& operator<<(std::ostream&, const HTTPRequest&);
/// Serialize HTTP response to output stream.
std::ostream& operator<<(std::ostream&, const HTTPResponse&);
/// Serialize HTTP method to output stream ("GET", "POST", etc.).
std::ostream& operator<<(std::ostream&, HTTPMethod);
/// Serialize HTTP status to output stream, include reason string ("200 OK" etc.)
std::ostream& operator<<(std::ostream&, HTTPStatus);


struct HTTPParserBase {
    const std::shared_ptr<util::Logger> logger_ptr;
    util::Logger& network_logger;

    // FIXME: Generally useful?
    struct CallocDeleter {
        void operator()(void* ptr)
        {
            std::free(ptr);
        }
    };

    HTTPParserBase(const std::shared_ptr<util::Logger>& logger_ptr)
        : logger_ptr{std::make_shared<util::CategoryLogger>(util::LogCategory::network, logger_ptr)}
        , network_logger{*logger_ptr}
    {
        // Allocating read buffer with calloc to avoid accidentally spilling
        // data from other sessions in case of a buffer overflow exploit.
        m_read_buffer.reset(static_cast<char*>(std::calloc(read_buffer_size, 1)));
    }
    virtual ~HTTPParserBase() {}

    std::string m_write_buffer;
    std::unique_ptr<char[], CallocDeleter> m_read_buffer;
    util::Optional<size_t> m_found_content_length;
    bool m_has_chunked_encoding = false;
    std::optional<std::stringstream> m_chunked_encoding_ss;
    static const size_t read_buffer_size = 8192;
    static const size_t max_header_line_length = read_buffer_size;

    /// Parses the contents of m_read_buffer as a HTTP header line,
    /// and calls on_header() as appropriate. on_header() will be called at
    /// most once per invocation.
    /// Returns false if the contents of m_read_buffer is not a valid HTTP
    /// header line.
    bool parse_header_line(size_t len);

    virtual std::error_code on_first_line(StringData line) = 0;
    virtual void on_header(StringData key, StringData value) = 0;
    virtual void on_body(StringData body) = 0;
    virtual void on_complete(std::error_code = std::error_code{}) = 0;

    /// If the input matches a known HTTP method string, return the appropriate
    /// HTTPMethod enum value. Otherwise, returns none.
    static util::Optional<HTTPMethod> parse_method_string(StringData method);

    /// Interpret line as the first line of an HTTP request. If the return value
    /// is true, out_method and out_uri have been assigned the appropriate
    /// values found in the request line.
    static bool parse_first_line_of_request(StringData line, HTTPMethod& out_method, StringData& out_uri);

    /// Interpret line as the first line of an HTTP response. If the return
    /// value is true, out_status and out_reason have been assigned the
    /// appropriate values found in the response line.
    static bool parse_first_line_of_response(StringData line, HTTPStatus& out_status, StringData& out_reason,
                                             util::Logger& logger);

    void set_write_buffer(const HTTPRequest&);
    void set_write_buffer(const HTTPResponse&);
};


template <class Socket>
struct HTTPParser : protected HTTPParserBase {
    explicit HTTPParser(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
        : HTTPParserBase(logger_ptr)
        , m_socket(socket)
    {
    }

    void read_first_line()
    {
        auto handler = [this](std::error_code ec, size_t n) {
            if (ec == util::error::operation_aborted) {
                return;
            }
            if (ec) {
                on_complete(ec);
                return;
            }
            ec = on_first_line(StringData(m_read_buffer.get(), n));
            if (ec) {
                on_complete(ec);
                return;
            }
            read_headers();
        };
        m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler));
    }

    void read_headers()
    {
        auto handler = [this](std::error_code ec, size_t n) {
            if (ec == util::error::operation_aborted) {
                return;
            }
            if (ec) {
                on_complete(ec);
                return;
            }
            if (n <= 2) {
                read_body();
                return;
            }
            if (!parse_header_line(n)) {
                on_complete(HTTPParserError::BadRequest);
                return;
            }

            // FIXME: Limit the total size of headers. Apache uses 8K.
            read_headers();
        };
        m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler));
    }

    void read_body()
    {
        if (m_found_content_length) {
            // FIXME: Support longer bodies.
            // FIXME: Support multipart and other body types (no body shaming).
            if (*m_found_content_length > read_buffer_size) {
                on_complete(HTTPParserError::ContentTooLong);
                return;
            }

            auto handler = [this](std::error_code ec, size_t n) {
                if (ec == util::error::operation_aborted) {
                    return;
                }
                if (!ec) {
                    on_body(std::string_view(m_read_buffer.get(), n));
                }
                on_complete(ec);
            };
            m_socket.async_read(m_read_buffer.get(), *m_found_content_length, std::move(handler));
        }
        else if (m_has_chunked_encoding) {
            auto content_length_handler = [this](std::error_code ec, size_t chunk_start_index) {
                if (ec == util::error::operation_aborted) {
                    on_complete(ec);
                    return;
                }

                auto content_length =
                    std::strtoul(std::string(m_read_buffer.get(), chunk_start_index - 2).c_str(), nullptr, 16);

                if (content_length == 0) {
                    on_body(m_chunked_encoding_ss->str());
                    on_complete(ec);
                    return;
                }

                auto handler = [this](std::error_code ec, size_t n) {
                    if (ec == util::error::operation_aborted) {
                        on_complete(ec);
                        return;
                    }
                    auto chunk_data = std::string_view(m_read_buffer.get(), n - 2); // -2 to strip \r\n
                    *m_chunked_encoding_ss << chunk_data;
                    read_body();
                };
                m_socket.async_read(m_read_buffer.get(), content_length + 2,
                                    std::move(handler)); // +2 to account for \r\n
            };

            // First get the content-length
            m_socket.async_read_until(m_read_buffer.get(), 8, '\n',
                                      content_length_handler); // buffer of 8 is enough to read hex value
        }
        else {
            // No body, just finish.
            on_complete();
        }
    }

    void write_buffer(util::UniqueFunction<void(std::error_code, size_t)> handler)
    {
        m_socket.async_write(m_write_buffer.data(), m_write_buffer.size(), std::move(handler));
    }

    Socket& m_socket;
};


template <class Socket>
struct HTTPClient : protected HTTPParser<Socket> {
    using Handler = void(HTTPResponse, std::error_code);

    explicit HTTPClient(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
        : HTTPParser<Socket>(socket, logger_ptr)
    {
    }

    /// Serialize and send \a request over the connected socket asynchronously.
    ///
    /// When the response has been received, or an error occurs, \a handler will
    /// be invoked with the appropriate parameters. The HTTPResponse object
    /// passed to \a handler will only be complete in non-error conditions, but
    /// may be partially populated.
    ///
    /// It is an error to start a request before the \a handler of a previous
    /// request has been invoked. It is permitted to call async_request() from
    /// the handler, unless an error has been reported representing a condition
    /// where the underlying socket is no longer able to communicate (for
    /// example, if it has been closed).
    ///
    /// If a request is already in progress, an exception will be thrown.
    ///
    /// This method is *NOT* thread-safe.
    void async_request(const HTTPRequest& request, util::UniqueFunction<Handler> handler)
    {
        if (REALM_UNLIKELY(m_handler)) {
            throw LogicError(ErrorCodes::LogicError, "Request already in progress.");
        }
        this->set_write_buffer(request);
        m_handler = std::move(handler);
        this->write_buffer([this](std::error_code ec, size_t bytes_written) {
            static_cast<void>(bytes_written);
            if (ec == util::error::operation_aborted) {
                return;
            }
            if (ec) {
                this->on_complete(ec);
                return;
            }
            this->read_first_line();
        });
    }

private:
    util::UniqueFunction<Handler> m_handler;
    HTTPResponse m_response;

    std::error_code on_first_line(StringData line) override final
    {
        HTTPStatus status;
        StringData reason;
        if (this->parse_first_line_of_response(line, status, reason, this->network_logger)) {
            m_response.status = status;
            m_response.reason = reason;
            return std::error_code{};
        }
        return HTTPParserError::MalformedResponse;
    }

    void on_header(StringData key, StringData value) override final
    {
        // FIXME: Multiple headers with the same key should show up as a
        // comma-separated list of their values, rather than overwriting.
        m_response.headers[std::string(key)] = std::string(value);
    }

    void on_body(StringData body) override final
    {
        m_response.body = std::string(body);
    }

    void on_complete(std::error_code ec) override final
    {
        auto handler = std::move(m_handler);
        m_handler = nullptr;
        handler(std::move(m_response), ec);
    }
};


template <class Socket>
struct HTTPServer : protected HTTPParser<Socket> {
    using RequestHandler = void(HTTPRequest, std::error_code);
    using RespondHandler = void(std::error_code);

    explicit HTTPServer(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
        : HTTPParser<Socket>(socket, logger_ptr)
    {
    }

    /// Receive a request on the underlying socket asynchronously.
    ///
    /// This function starts an asynchronous read operation and keeps reading
    /// until an HTTP request has been received. \a handler is invoked when a
    /// request has been received, or an error occurs.
    ///
    /// After a request is received, callers MUST invoke async_send_response()
    /// to provide the client with a valid HTTP response, unless the error
    /// passed to the handler represents a condition where the underlying socket
    /// is no longer able to communicate (for example, if it has been closed).
    ///
    /// It is an error to attempt to receive a request before any previous
    /// requests have been fully responded to, i.e. the \a handler argument of
    /// async_send_response() must have been invoked before attempting to
    /// receive the next request.
    ///
    /// This function is *NOT* thread-safe.
    void async_receive_request(util::UniqueFunction<RequestHandler> handler)
    {
        if (REALM_UNLIKELY(m_request_handler)) {
            throw LogicError(ErrorCodes::LogicError, "Request already in progress");
        }
        m_request_handler = std::move(handler);
        this->read_first_line();
    }

    /// Send an HTTP response to a client asynchronously.
    ///
    /// This function starts an asynchronous write operation on the underlying
    /// socket. \a handler is invoked when the response has been written to the
    /// socket, or an error occurs.
    ///
    /// It is an error to call async_receive_request() again before \a handler
    /// has been invoked, and it is an error to call async_send_response()
    /// before the \a handler of a previous invocation has been invoked.
    ///
    /// This function is *NOT* thread-safe.
    void async_send_response(const HTTPResponse& response, util::UniqueFunction<RespondHandler> handler)
    {
        if (REALM_UNLIKELY(!m_request_handler)) {
            throw LogicError(ErrorCodes::LogicError, "No request in progress");
        }
        if (m_respond_handler) {
            // FIXME: Proper exception type.
            throw LogicError(ErrorCodes::LogicError, "Already responding to request");
        }
        m_respond_handler = std::move(handler);
        this->set_write_buffer(response);
        this->write_buffer([this](std::error_code ec, size_t) {
            if (ec == util::error::operation_aborted) {
                return;
            }
            m_request_handler = nullptr;
            auto handler = std::move(m_respond_handler);
            handler(ec);
        });
        ;
    }

private:
    util::UniqueFunction<RequestHandler> m_request_handler;
    util::UniqueFunction<RespondHandler> m_respond_handler;
    HTTPRequest m_request;

    std::error_code on_first_line(StringData line) override final
    {
        HTTPMethod method;
        StringData uri;
        if (this->parse_first_line_of_request(line, method, uri)) {
            m_request.method = method;
            m_request.path = uri;
            return std::error_code{};
        }
        return HTTPParserError::MalformedRequest;
    }

    void on_header(StringData key, StringData value) override final
    {
        // FIXME: Multiple headers with the same key should show up as a
        // comma-separated list of their values, rather than overwriting.
        m_request.headers[std::string(key)] = std::string(value);
    }

    void on_body(StringData body) override final
    {
        m_request.body = std::string(body);
    }

    void on_complete(std::error_code ec) override final
    {
        // Deliberately not nullifying m_request_handler so that we can
        // check for invariants in async_send_response.
        m_request_handler(std::move(m_request), ec);
    }
};

} // namespace realm::sync
