#include "HttpSession.h"
#include "WebsocketSession.h"
#include <boost/config.hpp>
#include <boost/url/parse_path.hpp>
#include <boost/url/url_view.hpp>
#include <iostream>
#include <limits>

HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr<SharedState> &state)
    : m_stream(std::move(socket)), m_state(state) {
    //    m_buffer.reserve(1000 * 1000 * 1000);
}

void HttpSession::run() {
    doRead();
}

void HttpSession::errorReply(const Request &request, boost::beast::http::status status,
                             boost::beast::string_view message) {
    using namespace boost::beast;
    // invalid route
    http::response<http::string_body> res{status, request.version()};
    res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
    res.set(http::field::content_type, "text/html");
    res.keep_alive(request.keep_alive());
    res.body() = message;
    res.prepare_payload();

    reply(std::move(res));
}

void HttpSession::doRead() {
    // Construct a new parser for each message
    m_parser.emplace();

    // Apply a reasonable limit to the allowed size
    // of the body in bytes to prevent abuse.
    m_parser->body_limit(std::numeric_limits<std::uint64_t>::max());
    m_parser->header_limit(std::numeric_limits<std::uint32_t>::max());
    m_buffer.clear();

    // Set the timeout.
    m_stream.expires_after(std::chrono::seconds(30));
    boost::beast::http::async_read(
        m_stream, m_buffer, *m_parser,
        [self{shared_from_this()}](const boost::system::error_code &ec, std::size_t bytes_transferred) {
            self->onRead(ec, bytes_transferred);
        });
}

void HttpSession::onRead(boost::beast::error_code ec, std::size_t) {
    using namespace boost::beast;
    // This means they closed the connection
    if (ec == http::error::end_of_stream) {
        m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
        return;
    }
    if (ec) {
        if (ec == boost::asio::error::operation_aborted) return;
        LOG(info) << ec << " : " << ec.message();
        return;
    }

    auto &request = m_parser->get();
    // See if it is a WebSocket Upgrade
    if (websocket::is_upgrade(request)) {
        // Create a websocket session, transferring ownership
        // of both the socket and the HTTP request.
        auto session = std::make_shared<WebSocketSession>(m_stream.release_socket(), m_state);
        session->run(m_parser->release());
        return;
    }
    auto path = boost::urls::parse_path(request.target());
    if (!path) {
        LOG(error) << request.target() << "failed, error: " << path.error().message();
        errorReply(request, http::status::bad_request, "Illegal request-target");
        return;
    }
    boost::urls::matches matches;
    auto handler = m_state->find(*path, matches);
    if (handler) {
        (*handler)(*this, request, matches);
    } else {
        std::ostringstream oss;
        oss << "The resource '" << request.target() << "' was not found.";
        auto message = oss.str();
        errorReply(request, http::status::not_found, message);
        LOG(error) << message;
    }
}

void HttpSession::onWrite(boost::beast::error_code ec, std::size_t, bool close) {
    if (ec) {
        if (ec == boost::asio::error::operation_aborted) return;
        std::cerr << "write: " << ec.message() << "\n";
    }

    if (close) {
        // This means we should close the connection, usually because
        // the response indicated the "Connection: close" semantic.
        m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
        return;
    }

    // Read another request
    doRead();
}
