#include "Application.h"
#include "Base/Database.h"
#include "Base/HttpSession.h"
#include "Base/Messages.h"
#include "Core/IoContext.h"
#include "Core/MessageManager.h"
#include "Core/Singleton.h"
#include "Nng/Asio.h"
#include "Nng/Message.h"
#include "Nng/Socket.h"
#include "Router/router.hpp"
#include "ServiceLogic.h"
#include "SessionStore.h"
#include "Settings.h"
#include "WeChat/Corporation/Context.h"
#include "WebRTC/SignalServer.h"
#include <boost/asio/strand.hpp>

namespace Older {

class ApplicationPrivate {
public:
    void asyncReply() {
        reply->asyncReceive([this](const std::error_code &error, const Nng::Message &message) {
            if (error) {
                LOG(error) << error.message();
                return;
            }
            LOG(info) << "request: " << message.data<char>();
            std::string_view data("i am older server.");
            reply->send(data.data(), data.size());
            asyncReply();
        });
    }
    std::shared_ptr<boost::urls::router<RequestHandler>> router;
    std::shared_ptr<boost::asio::ip::tcp::acceptor> acceptor;
    std::unique_ptr<Nng::Socket> reply;
};

Application::Application() : m_d{new ApplicationPrivate()} {
    using namespace boost::urls;
    using namespace Core;
    m_d->router = std::make_shared<router<RequestHandler>>();

    m_messageManager = Singleton<MessageManager>::construct();
    m_settings = Singleton<Settings>::construct();

    m_ioContext = Singleton<IoContext>::construct(m_settings->threads());

    m_database = Singleton<Database>::construct();
    m_database->open(m_settings->sqlitePath());
    m_sessionStore = Singleton<SessionStore>::construct(*m_ioContext->ioContext());

    // clang-format off
    m_messageManager->subscribe<RegisterUrlHandler>([this](std::string_view url, RequestHandler handler) { 
        LOG(info)<<"register url: "<<url;
        m_d->router->insert(url, handler); 
    });
    // clang-format on

    m_corporationContext = Singleton<WeChat::Corporation::Context>::construct(*m_ioContext->ioContext());
    m_corporationContext->start();

    m_signalServer = std::make_shared<SignalServer>(*this);

    m_d->router->insert("/api/v1/notify", [this](HttpSession &session, const HttpRequest &request, const matches &matches) {
        auto manager = Singleton<MessageManager>::instance();
        if (manager) {
            manager->publish<NotifyServerChan>(request);
        }
        session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
    });

    m_d->reply = std::make_unique<Nng::Socket>(*m_ioContext->ioContext(), Nng::Protocol::Reply);
    m_d->reply->listen("tcp://127.0.0.1:8985");
    m_d->asyncReply();
}

Application::~Application() {
    if (m_d != nullptr) {
        delete m_d;
    }
}

boost::asio::io_context &Application::ioContext() {
    return *m_ioContext->ioContext();
}

void Application::startAcceptHttpConnections(const std::string &address, uint16_t port) {
    m_d->acceptor = std::make_shared<boost::asio::ip::tcp::acceptor>(*m_ioContext->ioContext());
    boost::beast::error_code error;
    boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::make_address(address), port);
    m_d->acceptor->open(endpoint.protocol(), error);
    if (error) {
        LOG(error) << error.message();
        return;
    }
    m_d->acceptor->set_option(boost::asio::socket_base::reuse_address(true), error);
    if (error) {
        LOG(error) << error.message();
        return;
    }
    m_d->acceptor->bind(endpoint, error);
    if (error) {
        LOG(error) << error.message();
        return;
    }
    m_d->acceptor->listen(boost::asio::socket_base::max_listen_connections, error);
    if (error) {
        LOG(error) << error.message();
        return;
    }
    asyncAcceptHttpConnections();
}

void Application::insertUrl(std::string_view url, RequestHandler &&handler) {
    m_d->router->insert(url, std::move(handler));
}

int Application::exec() {
    using namespace Core;
    auto settings = Singleton<Settings>::instance();
    ServiceLogic::live2dBackend();
    ServiceLogic::visitAnalysis();
    ServiceLogic::userAccount();
    ServiceLogic::staticFilesDeploy();
    startAcceptHttpConnections(settings->server(), settings->port());
    m_ioContext->run();
    LOG(info) << "app exit, code: " << m_returnCode;
    return 0;
}

void Application::exit(int returnCode) {
    if (m_returnCode != returnCode) {
        m_returnCode = returnCode;
    }
    if (m_ioContext) {
        m_ioContext->stop();
    }
}

void Application::asyncAcceptHttpConnections() {
    auto socket = std::make_shared<boost::asio::ip::tcp::socket>(boost::asio::make_strand(*m_ioContext->ioContext()));
    m_d->acceptor->async_accept(*socket, [self{shared_from_this()}, socket](const boost::system::error_code &error) {
        if (error) {
            if (error == boost::asio::error::operation_aborted) return;
            LOG(error) << error.message();
        } else {
            auto session = std::make_shared<HttpSession>(std::move(*socket), self->m_d->router);
            session->run();
        }
        self->asyncAcceptHttpConnections();
    });
}
} // namespace Older
