#include "HttpSession.h"
#include "Core/Logger.h"
#include <boost/beast/http/read.hpp>
#include <boost/beast/version.hpp>
#include <boost/config.hpp>
#include <boost/stacktrace.hpp>
#include <boost/url/parse_path.hpp>
#include <boost/url/url_view.hpp>
#include <iostream>
#include <limits>

namespace Danki {
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket,
                         const std::shared_ptr<boost::urls::router<RequestHandler>> &router)
    : m_stream(std::move(socket)), m_router(router) {
}

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

boost::beast::tcp_stream::executor_type HttpSession::executor() {
    return m_stream.get_executor();
}

boost::asio::ip::tcp::socket HttpSession::releaseSocket() {
    return m_stream.release_socket();
}

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));
    // clang-format off
    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);
    });
    // clang-format on
}

void HttpSession::onRead(const boost::beast::error_code &error, std::size_t) {
    using namespace boost::beast;
    if (error) {
        if (error == http::error::end_of_stream) {
            boost::beast::error_code e;
            m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, e);
        } else if (error != boost::asio::error::operation_aborted) {
            LOG(info) << error << " : " << error.message();
        }
        return;
    } else if (m_router.expired()) {
        LOG(error) << "router is null.";
        return;
    }

    auto &request = m_parser->get();
    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;
    }
    auto router = m_router.lock();
    boost::urls::matches matches;
    auto handler = router->find(*path, matches);
    if (handler) {
        try {
            (*handler)(*this, request, matches);
        } catch (const std::exception &e) {
            boost::stacktrace::stacktrace trace = boost::stacktrace::stacktrace::from_current_exception();
            LOG(error) << e.what() << ", trace:\n" << trace;
        }
    } 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();
}
} // namespace Danki
