#include "ZoneServer.hpp"

#include "Cluster.pb.h"
#include "monitoring/NullMetricsReporter.hpp"
#include "monitoring/TimescaleDbMetricsReporter.hpp"
#include "runtime/LockStep.hpp"

#include <csignal>
#include <cstdlib>
#include <spdlog/spdlog.h>
#include <tracy/Tracy.hpp>

namespace tw::net {

static std::unique_ptr<NetworkMetricsReporter> make_reporter(const ZoneServerConfiguration& config) {
    if (config.timescaledb)
        return std::make_unique<TimescaleDbMetricsReporter>(*config.timescaledb);
    return std::make_unique<NullMetricsReporter>();
}

ZoneServer::ZoneServer(ZoneServerConfiguration config)
:
    m_metrics_reporter(make_reporter(config)),
    m_player_session_registry(std::make_unique<PlayerSessionRegistry>()),
    m_network_receiver(std::make_unique<NetworkReceiver>(
        m_player_session_registry.get(), config.quicr_port, m_metrics_reporter.get()
    )),
    m_replicator(std::make_unique<StateReplicator<SpatialBackendType>>(
        m_player_session_registry.get(),
        m_network_receiver.get()
    )),
    m_cluster_link(config.cluster_port),
    m_coordinator("127.0.0.1", config.cluster_port)
{
    auto [assigned_id, area_bounds, peers] = m_coordinator.register_zone();
    m_own_zone_id = assigned_id;

    m_zones.push_back(std::make_unique<ZoneManager>(area_bounds));

    spdlog::info("Registered as zone {} ({},{}) ({},{})",
        assigned_id,
        area_bounds.min.x, area_bounds.min.y,
        area_bounds.max.x, area_bounds.max.y);

    mmo::cluster::ZoneHello hello;
    hello.mutable_zone()->set_id(assigned_id);
    hello.mutable_zone()->mutable_bounds()->set_min_x(area_bounds.min.x);
    hello.mutable_zone()->mutable_bounds()->set_min_z(area_bounds.min.y);
    hello.mutable_zone()->mutable_bounds()->set_max_x(area_bounds.max.x);
    hello.mutable_zone()->mutable_bounds()->set_max_z(area_bounds.max.y);

    for (const auto& peer : peers) {
        spdlog::info("Connecting to peer zone {} at {}:{}", peer.id, peer.host, peer.port);
        auto* conn = m_cluster_link.connect_to_peer(peer.host, peer.port);
        if (conn)
            m_cluster_link.send_mesg(conn, hello);
    }
}

void ZoneServer::player_update_handler(SessionId session_id, mmo::PlayerMoveMessage message) {
    auto it = m_session_zone.find(session_id);
    if (it == m_session_zone.end()) return;

    if (auto* session = m_player_session_registry->session(session_id)) {
        session->last_received_frame = message.frame_idx();
    }

    it->second->on_player_move(session_id, std::move(message));
}

static std::atomic<bool> quit(false);

static void got_signal(int) {
    quit.store(true);
}

static void register_signal_handler() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = got_signal;
    sigfillset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
}

void ZoneServer::update_clients(uint32_t frame_idx) {
    m_network_receiver->update();

    while (m_network_receiver->peek_new_session()) {
        auto session_id = m_network_receiver->pop_new_session();

        glm::vec3 position = glm::vec3(
            (float)std::rand() / RAND_MAX * 20.0f,
            10.0f,
            (float)std::rand() / RAND_MAX * 20.0f
        );

        ZoneManager* zone = m_zones.front().get();
        entt::entity entity = zone->spawn_entity({ .name = "Pepik", .position = position });
        zone->add_client(session_id, entity);
        m_session_zone[session_id] = zone;

        mmo::SetControlledEntity mesg = {};
        mesg.set_entity_id((uint32_t)entity);
        m_network_receiver->send_mesg(session_id, mesg);

        spdlog::info("Client {} connected", session_id);
    }
}

void ZoneServer::run() {
    LockStep lock_step(20);
    register_signal_handler();

    uint32_t frame_idx = 1;

    m_network_receiver->set_handler<mmo::PlayerMoveMessage>(
        [this](SessionId session_id, const mmo::PlayerMoveMessage& mesg) {
            player_update_handler(session_id, mesg);
        });

    m_network_receiver->set_handler<mmo::chat::SendChatMessageRequest>(
        [this](SessionId session_id, const mmo::chat::SendChatMessageRequest& mesg) {
        });

    while (!quit.load()) {
        if (lock_step.wait_for_next_step()) { continue; }

        m_cluster_link.update();

        FrameMarkStart("Update clients");
        update_clients(frame_idx);
        FrameMarkEnd("Update clients");

        for (auto& zone : m_zones) {
            zone->update(frame_idx, lock_step.delta_time());

            for (const auto& [session_id, session_zone] : m_session_zone) {
                if (session_zone != zone.get()) continue;
                if (auto* session = m_player_session_registry->session(session_id)) {
                    session->acked_frame = zone->acked_input_frame(session_id);
                }
            }

            m_replicator->replicate(zone->registry(), zone->interest());
        }

        m_network_receiver->update();

        m_metrics_reporter->set_player_count(m_session_zone.size());
        m_metrics_reporter->tick();

        frame_idx++;
        FrameMark;
    }

    // Notify all peers that this zone server is going away.
    for (auto& zone : m_zones) {
        mmo::cluster::ZoneBye bye;
        bye.mutable_zone()->set_id(m_own_zone_id);
        bye.mutable_zone()->mutable_bounds()->set_min_x(zone->area().min.x);
        bye.mutable_zone()->mutable_bounds()->set_min_z(zone->area().min.y);
        bye.mutable_zone()->mutable_bounds()->set_max_x(zone->area().max.x);
        bye.mutable_zone()->mutable_bounds()->set_max_z(zone->area().max.y);
        m_cluster_link.broadcast(bye);
    }
}

} // namespace tw::net
