#include "ChatServerController.hpp"

#include "Chat.pb.h"
#include "MessageRegistry.hpp"
#include <spdlog/spdlog.h>
#include <stdexcept>

namespace tw::chat {

static mmo::chat::ChatErrorCode to_error_code(tl::expected<void, ChatServerError> result) {
    if (result) return mmo::chat::CHAT_ERROR_CODE_OK;
    switch (result.error()) {
        case ChatServerError::PermissionDenied: return mmo::chat::CHAT_ERROR_CODE_NOT_IN_CHANNEL;
        case ChatServerError::ChannelNotFound:  return mmo::chat::CHAT_ERROR_CODE_CHANNEL_NOT_FOUND;
    }
    return mmo::chat::CHAT_ERROR_CODE_CHANNEL_NOT_FOUND;
}

static std::unique_ptr<msg::MessageEndpoint> bind_endpoint(int port) {
    auto endpoint_r = msg::MessageEndpoint::bind(port);
    if (!endpoint_r) {
        throw std::runtime_error("Chat server failed to bind to port " + std::to_string(port) +
                                 ": " + endpoint_r.error().message());
    }

    return std::move(endpoint_r.value());
}

ChatServerController::ChatServerController(int port)
    : m_endpoint(bind_endpoint(port))
    , m_messages(m_endpoint.get())
    , m_service([this](uint64_t id, const ChatMessage& msg) { broadcast(id, msg); })
{
    m_endpoint->set_on_peer_connected([](msg::PeerId client_id) {
        spdlog::info("Chat client connected: {}", client_id);
    });

    register_handlers();
    spdlog::info("Chat server listening on port {}", port);
}

void ChatServerController::register_handlers() {
    m_messages.set_handler<mmo::chat::SendChatMessageRequest>(
        [this](msg::PeerId client_id, const mmo::chat::SendChatMessageRequest& msg) {
            mmo::chat::SendChatMessageResponse r;
            r.set_channel_id(msg.channel_id());
            r.set_error(to_error_code(m_service.send_message(client_id, msg.channel_id(), msg.message())));
            (void)m_messages.send_to(client_id, r, false);
        });

    m_messages.set_handler<mmo::chat::JoinChannelRequest>(
        [this](msg::PeerId client_id, const mmo::chat::JoinChannelRequest& msg) {
            m_service.join_channel(client_id, msg.channel_id());
            mmo::chat::JoinChannelResponse r;
            r.set_channel_id(msg.channel_id());
            r.set_error(mmo::chat::CHAT_ERROR_CODE_OK);
            (void)m_messages.send_to(client_id, r, false);
        });

    m_messages.set_handler<mmo::chat::LeaveChannelRequest>(
        [this](msg::PeerId client_id, const mmo::chat::LeaveChannelRequest& msg) {
            m_service.leave_channel(client_id, msg.channel_id());
            mmo::chat::LeaveChannelResponse r;
            r.set_channel_id(msg.channel_id());
            r.set_error(mmo::chat::CHAT_ERROR_CODE_OK);
            (void)m_messages.send_to(client_id, r, false);
        });
}

void ChatServerController::update() {
    m_endpoint->update();
}

void ChatServerController::broadcast(msg::PeerId client_id, const ChatMessage& msg) {
    mmo::chat::ChatMessageBroadcastRequest bcast;
    bcast.set_channel_id(msg.channel_id);
    bcast.set_sender_id(msg.client_id);
    bcast.set_message(msg.message);

    (void)m_messages.send_to(client_id, bcast, true);
}

} // namespace tw::chat
