#include "TestPeerNode.hpp"
#include "QuicrPeerLink.hpp"
#include "TcpPeerLink.hpp"
#include "runtime/LockStep.hpp"

#include <atomic>
#include <csignal>
#include <iostream>
#include <memory>
#include <spdlog/spdlog.h>
#include <string>

// ── signal handling ───────────────────────────────────────────────────────────

static std::atomic<bool> g_running{true};

static void on_signal(int) {
    g_running.store(false);
}

// ── CLI ───────────────────────────────────────────────────────────────────────

enum class Transport { Quicr, Tcp };

struct Config {
    uint32_t    id           = 0;
    uint16_t    port         = 0;
    std::string registry     = "";
    uint32_t    peer_count   = 0;   // total number of nodes to wait for
    int         ticks        = 0;   // 0 = run until SIGINT
    Transport   transport    = Transport::Quicr;
};

static void print_usage(const char* prog) {
    std::cerr
        << "Usage: " << prog
        << " --id <N> --port <N> --registry <path> --peers <N> [--ticks <N>]\n"
        << "\n"
        << "  --id        Unique node ID (uint, > 0)\n"
        << "  --port      UDP port this node listens on\n"
        << "  --registry  Path to shared CSV registry file\n"
        << "  --peers     Total number of nodes that will participate\n"
        << "  --ticks     How many simulation frames to run (default: run until SIGINT)\n"
        << "  --transport quicr|tcp  Transport backend (default: quicr)\n";
}

static bool parse_args(int argc, char* argv[], Config& cfg) {
    for (int i = 1; i < argc; ++i) {
        std::string a = argv[i];
        if (a == "--help" || a == "-h") return false;

        auto need_next = [&]() -> const char* {
            if (i + 1 >= argc) { std::cerr << a << " requires a value\n"; return nullptr; }
            return argv[++i];
        };

        if      (a == "--id")        { auto v = need_next(); if (!v) return false; cfg.id         = std::stoul(v); }
        else if (a == "--port")      { auto v = need_next(); if (!v) return false; cfg.port       = static_cast<uint16_t>(std::stoul(v)); }
        else if (a == "--registry")  { auto v = need_next(); if (!v) return false; cfg.registry   = v; }
        else if (a == "--peers")     { auto v = need_next(); if (!v) return false; cfg.peer_count = std::stoul(v); }
        else if (a == "--ticks")     { auto v = need_next(); if (!v) return false; cfg.ticks      = std::stoi(v); }
        else if (a == "--transport") {
            auto v = need_next(); if (!v) return false;
            std::string t = v;
            if      (t == "quicr") cfg.transport = Transport::Quicr;
            else if (t == "tcp")   cfg.transport = Transport::Tcp;
            else { std::cerr << "Unknown transport: " << t << " (use quicr or tcp)\n"; return false; }
        }
        else { std::cerr << "Unknown argument: " << a << "\n"; return false; }
    }

    if (cfg.id == 0 || cfg.port == 0 || cfg.registry.empty() || cfg.peer_count == 0) {
        std::cerr << "Error: --id, --port, --registry, and --peers are all required\n";
        return false;
    }
    return true;
}

// ── main ──────────────────────────────────────────────────────────────────────

int main(int argc, char* argv[]) {
    Config cfg;
    if (!parse_args(argc, argv, cfg)) {
        print_usage(argv[0]);
        return 1;
    }

    std::signal(SIGINT,  on_signal);
    std::signal(SIGTERM, on_signal);

    spdlog::set_level(spdlog::level::info);
    spdlog::info("[p2p] node {} starting on port {}", cfg.id, cfg.port);

    // ── setup ─────────────────────────────────────────────────────────────────

    const char* ext = (cfg.transport == Transport::Tcp) ? "tcp" : "quicr";
    std::string csv_path = "/tmp/peer_node_" + std::to_string(cfg.id) + "." + ext + ".csv";

    std::unique_ptr<tw::p2p::PeerLink> link;
    if (cfg.transport == Transport::Tcp)
        link = std::make_unique<tw::p2p::TcpPeerLink>(cfg.id, tw::net::Address{std::nullopt, cfg.port});
    else
        link = std::make_unique<tw::p2p::QuicrPeerLink>(cfg.id, cfg.port);

    tw::p2p::TestPeerNode node(cfg.id, cfg.port, std::move(link), cfg.registry, csv_path);

    node.register_self();
    spdlog::info("[p2p] node {} registered; waiting for {} peers …", cfg.id, cfg.peer_count);

    if (!node.wait_until_peers_registered(cfg.peer_count)) {
        spdlog::error("[p2p] node {}: timed out waiting for peers — aborting", cfg.id);
        return 1;
    }

    spdlog::info("[p2p] node {}: all {} peers registered; connecting …", cfg.id, cfg.peer_count);
    node.discover_and_connect();
    node.wait_for_connections(cfg.peer_count - 1, std::chrono::milliseconds(5000));

    spdlog::info("[p2p] node {}: connections up — starting simulation", cfg.id);

    // ── simulation loop (fixed 60 Hz) ─────────────────────────────────────────

    tw::LockStep lock_step(60);

    while (g_running.load()) {
        if (lock_step.wait_for_next_step()) continue;

        node.tick();

        if (cfg.ticks > 0 &&
            static_cast<int>(node.stepped_frames()) >= cfg.ticks)
        {
            break;
        }
    }

    // ── summary ───────────────────────────────────────────────────────────────

    spdlog::info("[p2p] node {}  frames={} rollbacks={} rollback_rate={:.1f}%",
                 cfg.id,
                 node.stepped_frames(),
                 node.rollbacks(),
                 100.0 * static_cast<double>(node.rollbacks()) /
                     static_cast<double>(node.stepped_frames() + 1));

    return 0;
}
