#include "crow.h"
#include "routes.h"
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <sstream>
#include <string>
#include <sys/resource.h>
#include <sys/utsname.h>
#include <thread>
#include <unistd.h>

#if defined(__APPLE__)
#  include <mach/mach.h>
#  include <sys/sysctl.h>
#endif

namespace routes {

namespace {

std::string env_or(const char* k, const char* fallback) {
    if (const char* v = std::getenv(k)) return std::string(v);
    return std::string(fallback);
}

long long boot_epoch() {
    static long long boot = std::time(nullptr);
    return boot;
}

// Per-process resident memory in bytes, best-effort across Linux/macOS.
long long rss_bytes() {
#if defined(__APPLE__)
    mach_task_basic_info info{};
    mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
    if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
                  reinterpret_cast<task_info_t>(&info), &count) == KERN_SUCCESS) {
        return static_cast<long long>(info.resident_size);
    }
    return 0;
#else
    std::ifstream f("/proc/self/status");
    std::string line;
    while (std::getline(f, line)) {
        if (line.rfind("VmRSS:", 0) == 0) {
            std::istringstream ss(line.substr(6));
            long long kb = 0;
            ss >> kb;
            return kb * 1024;
        }
    }
    return 0;
#endif
}

// 1/5/15-minute load average (returns 0s on platforms without getloadavg).
void load_avg(double out[3]) {
    out[0] = out[1] = out[2] = 0.0;
#if defined(_SC_NPROCESSORS_ONLN) || defined(__APPLE__) || defined(__linux__)
    double a[3] = {0, 0, 0};
    if (getloadavg(a, 3) == 3) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; }
#endif
}

long long total_memory_bytes() {
#if defined(__APPLE__)
    int mib[2] = {CTL_HW, HW_MEMSIZE};
    int64_t mem = 0;
    size_t len = sizeof(mem);
    if (sysctl(mib, 2, &mem, &len, nullptr, 0) == 0) {
        return static_cast<long long>(mem);
    }
    return 0;
#else
    std::ifstream f("/proc/meminfo");
    std::string line;
    while (std::getline(f, line)) {
        if (line.rfind("MemTotal:", 0) == 0) {
            std::istringstream ss(line.substr(9));
            long long kb = 0; ss >> kb;
            return kb * 1024;
        }
    }
    return 0;
#endif
}

} // anonymous namespace

void register_system(crow::SimpleApp& app) {

    // /api/version — build identity
    CROW_ROUTE(app, "/api/version")
    ([]() {
        crow::json::wvalue r;
        r["name"]       = "CxWebApp";
        r["version"]    = env_or("CXWEBAPP_VERSION", "1.1.0");
        r["git_sha"]    = env_or("CXWEBAPP_GIT_SHA", "unknown");
        r["build_time"] = env_or("CXWEBAPP_BUILD_TIME", "");
        return r;
    });

    // /api/system — runtime/system facts (safe to expose, no secrets)
    CROW_ROUTE(app, "/api/system")
    ([]() {
        crow::json::wvalue r;
        struct utsname u{};
        if (uname(&u) == 0) {
            r["sysname"]  = u.sysname;
            r["release"]  = u.release;
            r["machine"]  = u.machine;
            r["nodename"] = u.nodename;
        }
        char host[256] = {0};
        if (gethostname(host, sizeof(host) - 1) == 0) {
            r["hostname"] = std::string(host);
        }
        r["pid"]            = static_cast<long>(::getpid());
        r["uptime_seconds"] = static_cast<long>(std::time(nullptr) - boot_epoch());
        r["cxx_standard"]   = static_cast<int>(__cplusplus);
        r["cpu_count"]      = static_cast<int>(std::thread::hardware_concurrency());

        long long rss = rss_bytes();
        long long total = total_memory_bytes();
        r["rss_bytes"]   = static_cast<double>(rss);
        r["mem_total"]   = static_cast<double>(total);
        if (total > 0) r["mem_used_pct"] = static_cast<double>(rss) / static_cast<double>(total) * 100.0;

        double la[3]; load_avg(la);
        crow::json::wvalue::list lvec;
        lvec.emplace_back(la[0]); lvec.emplace_back(la[1]); lvec.emplace_back(la[2]);
        r["loadavg"] = std::move(lvec);

        struct rusage ru{};
        if (getrusage(RUSAGE_SELF, &ru) == 0) {
            r["user_cpu_s"]   = static_cast<double>(ru.ru_utime.tv_sec) + ru.ru_utime.tv_usec / 1e6;
            r["system_cpu_s"] = static_cast<double>(ru.ru_stime.tv_sec) + ru.ru_stime.tv_usec / 1e6;
            r["max_rss_kb"]   = static_cast<double>(ru.ru_maxrss);
        }
        return r;
    });

    // /api/echo — POST anything, get it back (debug helper)
    CROW_ROUTE(app, "/api/echo").methods(crow::HTTPMethod::POST)
    ([](const crow::request& req) {
        crow::json::wvalue r;
        r["method"]  = "POST";
        r["len"]     = static_cast<int>(req.body.size());
        r["body"]    = req.body;
        return crow::response(r);
    });

    // /ws/echo — minimal WebSocket echo room (per-connection state)
    CROW_WEBSOCKET_ROUTE(app, "/ws/echo")
        .onopen([](crow::websocket::connection& conn) {
            conn.send_text("welcome");
        })
        .onmessage([](crow::websocket::connection& conn,
                      const std::string& data, bool is_binary) {
            if (is_binary) conn.send_binary(data);
            else           conn.send_text(data);
        })
        .onclose([](crow::websocket::connection&,
                    const std::string&) {});
}

} // namespace routes
