// engine_subsystems.cpp — All subsystem manager implementations
// Ported from v1-monolithic/src/engine.cpp lines 1-426 → Iter2 modular architecture
// ThreadPool, HbMgr, WdMgr, DdMon, FoMgr, ScMgr, FwMgr, LdMgr

#include "engine_subsystems.h"
#include "engine_bridge.h"

#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/inotify.h>
#include <arpa/inet.h>
#include <unistd.h>

using namespace node669427::subsys;

// ──────────────────────────────────────────────────────────────
// SubsysThreadPool (renamed to avoid collision with engine_http.h ThreadPool)
// ──────────────────────────────────────────────────────────────

SubsysThreadPool::SubsysThreadPool(size_t n) {
    workers_.reserve(n);
    for (size_t i = 0; i < n; ++i) {
        workers_.emplace_back([this] {
            for (;;) {
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(mutex_);
                    cv_.wait(lock, [this] { return shutdown_ || !tasks_.empty(); });
                    if (shutdown_ && tasks_.empty()) return;
                    task = std::move(tasks_.front());
                    tasks_.pop();
                }
                try { task(); } catch (const std::exception& e) { LOG(ERROR, "pool: " << e.what()); }
            }
        });
    }
    LOG(INFO, "pool: " << n << " workers");
}

SubsysThreadPool::~SubsysThreadPool() {
    { std::lock_guard<std::mutex> lock(mutex_); shutdown_ = true; }
    cv_.notify_all();
    for (auto& w : workers_) if (w.joinable()) w.join();
    LOG(INFO, "pool: shutdown");
}

// ──────────────────────────────────────────────────────────────
// HbMgr — Heartbeat Manager
// ──────────────────────────────────────────────────────────────

HbMgr::HbMgr(OriginBridge* b, EngineConfig* c) : bridge_(b), cfg_(c) {}
HbMgr::~HbMgr() { stop(); }

void HbMgr::start() {
    if (running_.exchange(true)) return;
    thread_ = std::thread(&HbMgr::run_loop, this);
    LOG(INFO, "hb: started");
}

void HbMgr::stop() {
    if (!running_.exchange(false)) return;
    if (thread_.joinable()) thread_.join();
    LOG(INFO, "hb: stopped");
}

void HbMgr::run_loop() {
    while (running_.load()) {
        if (bridge_) {
            if (!bridge_->send_heartbeat(cpu_pct_.load(), mem_pct_.load(), disk_pct_.load(),
                    wg_peers_.load(), ServiceState::HEALTHY, cfg_->node_role)) {
                LOG(WARN, "hb: send failed");
            }
        }
        for (uint32_t i = 0; i < cfg_->heartbeat_interval_s && running_.load(); ++i)
            std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

// ──────────────────────────────────────────────────────────────
// WdMgr — Watchdog Manager
// ──────────────────────────────────────────────────────────────

WdMgr::WdMgr(EngineConfig* c) : cfg_(c) {}
WdMgr::~WdMgr() { stop(); }

void WdMgr::start() {
    if (running_.exchange(true)) return;
    thread_ = std::thread(&WdMgr::run_loop, this);
    LOG(INFO, "wd: started");
}

void WdMgr::stop() {
    if (!running_.exchange(false)) return;
    if (thread_.joinable()) thread_.join();
    LOG(INFO, "wd: stopped");
}

void WdMgr::run_loop() {
    uint32_t chattr_cnt = 0;
    while (running_.load()) {
        if (++chattr_cnt >= cfg_->chattr_check_interval_s) {
            chattr_cnt = 0;
            // Check immutable attribute on scripts directories using fork/execv (no shell injection)
            auto check_immutable = [](const std::string& path) {
                int pipefd[2];
                if (pipe(pipefd) != 0) return;
                pid_t pid = fork();
                if (pid == 0) {
                    // child
                    ::close(pipefd[0]);
                    dup2(pipefd[1], STDOUT_FILENO);
                    dup2(pipefd[1], STDERR_FILENO);
                    ::close(pipefd[1]);
                    execlp("lsattr", "lsattr", "-d", path.c_str(), (char*)nullptr);
                    _exit(127);
                } else if (pid > 0) {
                    ::close(pipefd[1]);
                    char b[256] = {0};
                    ssize_t n = read(pipefd[0], b, sizeof(b) - 1);
                    ::close(pipefd[0]);
                    int status;
                    waitpid(pid, &status, 0);
                    if (n > 0 && !strstr(b, "i")) {
                        LOG(WARN, "wd: " << path << " is mutable");
                    }
                } else {
                    ::close(pipefd[0]);
                    ::close(pipefd[1]);
                }
            };
            check_immutable(cfg_->scripts_dir);
            check_immutable(cfg_->scripts_enc);
        }
        if (access(E_PID, F_OK) != 0) LOG(WARN, "wd: PID file missing");
        for (uint32_t i = 0; i < cfg_->watchdog_interval_s && running_.load(); ++i)
            std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

// ──────────────────────────────────────────────────────────────
// DdMon — DDoS Monitor
// ──────────────────────────────────────────────────────────────

DdMon::DdMon(EngineConfig* c) : cfg_(c) {}
DdMon::~DdMon() { stop(); }

void DdMon::start() {
    if (running_.exchange(true)) return;
    thread_ = std::thread(&DdMon::run_loop, this);
    LOG(INFO, "dd: started");
}

void DdMon::stop() {
    if (!running_.exchange(false)) return;
    if (thread_.joinable()) thread_.join();
    LOG(INFO, "dd: stopped");
}

void DdMon::run_loop() {
    while (running_.load()) {
        uint32_t syn = syn_pps_.exchange(0);
        uint32_t udp = udp_pps_.exchange(0);
        uint32_t http5xx = http_5xx_per_sec_.exchange(0);
        uint32_t bw = bw_mbps_.exchange(0);
        AttackType detected = AttackType::NONE;
        if (syn > cfg_->ddos_syn_threshold) {
            detected = AttackType::DDOS_SYN;
            LOG(WARN, "dd: SYN flood " << syn << " pps");
        } else if (udp > cfg_->ddos_udp_threshold) {
            detected = AttackType::DDOS_UDP;
            LOG(WARN, "dd: UDP flood " << udp << " pps");
        } else if (http5xx > cfg_->ddos_http_err_threshold) {
            detected = AttackType::L7_HTTP;
            LOG(WARN, "dd: HTTP flood " << http5xx << " 5xx/s");
        } else if (bw > cfg_->ddos_bw_threshold_mb) {
            detected = AttackType::DDOS_ICMP;
            LOG(WARN, "dd: BW flood " << bw << " MB/s");
        }
        if (detected != AttackType::NONE) {
            current_attack_.store(static_cast<uint8_t>(detected));
        } else if (current_attack_.load() != static_cast<uint8_t>(AttackType::NONE)) {
            LOG(INFO, "dd: attack cleared");
            current_attack_.store(static_cast<uint8_t>(AttackType::NONE));
        }
        std::this_thread::sleep_for(std::chrono::seconds(cfg_->ddos_check_interval_s));
    }
}

// ──────────────────────────────────────────────────────────────
// FoMgr — Failover Manager (FIXED: fork/execv instead of popen for ping)
// ──────────────────────────────────────────────────────────────

FoMgr::FoMgr(OriginBridge* b, EngineConfig* c) : bridge_(b), cfg_(c) {}
FoMgr::~FoMgr() { stop(); }

void FoMgr::start() {
    if (running_.exchange(true)) return;
    thread_ = std::thread(&FoMgr::run_loop, this);
    LOG(INFO, "fo: started");
}

void FoMgr::stop() {
    if (!running_.exchange(false)) return;
    if (thread_.joinable()) thread_.join();
    LOG(INFO, "fo: stopped");
}

// Safe ping check using fork/execv — no shell, no command injection
static bool safe_ping(const std::string& host) {
    // Validate host: only allow alphanumeric, dots, colons (IPv6), hyphens
    for (char c : host) {
        if (!isalnum(c) && c != '.' && c != ':' && c != '-') return false;
    }
    int pipefd[2];
    if (pipe(pipefd) != 0) return false;
    pid_t pid = fork();
    if (pid == 0) {
        // child
        ::close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        ::close(STDERR_FILENO);
        ::close(pipefd[1]);
        execlp("ping", "ping", "-c", "1", "-W", "3", host.c_str(), (char*)nullptr);
        _exit(127);
    } else if (pid > 0) {
        ::close(pipefd[1]);
        char buf[4096] = {0};
        ssize_t n = read(pipefd[0], buf, sizeof(buf) - 1);
        ::close(pipefd[0]);
        int status;
        waitpid(pid, &status, 0);
        if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return true;
        if (n > 0 && strstr(buf, "1 received")) return true;
        return false;
    } else {
        ::close(pipefd[0]);
        ::close(pipefd[1]);
        return false;
    }
}

void FoMgr::run_loop() {
    while (running_.load()) {
        if (failover_active_.load()) {
            std::string node;
            { std::lock_guard<std::mutex> lock(failover_mutex_); node = failed_node_; }
            if (!node.empty()) {
                bool recovered = safe_ping(node);
                if (recovered) { LOG(INFO, "fo: recovered " << node); recover_node(node); }
            }
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(cfg_->failover_timeout_ms));
    }
}

bool FoMgr::trigger_failover(const std::string& node) {
    std::lock_guard<std::mutex> lock(failover_mutex_);
    LOG(INFO, "fo: failover " << node);
    failed_node_ = node;
    failover_active_.store(true);
    if (bridge_) {
        nlohmann::json r;
        r["event"] = "failover";
        r["node"] = node;
        r["timestamp"] = now_epoch();
        std::string b = r.dump();
        bridge_->sign_request("POST", "/api/events/failover", b);
    }
    return true;
}

bool FoMgr::recover_node(const std::string& node) {
    std::lock_guard<std::mutex> lock(failover_mutex_);
    LOG(INFO, "fo: recover " << node);
    failed_node_.clear();
    failover_active_.store(false);
    return true;
}

// ──────────────────────────────────────────────────────────────
// ScMgr — Script Manager (TOOL_ID-aware)
// Loads tool_map from engine.json; dispatches by TOOL_ID or filename
// ──────────────────────────────────────────────────────────────

ScMgr::ScMgr(EngineConfig* c) : cfg_(c) {}
ScMgr::~ScMgr() { stop(); if (watch_fd_ >= 0) ::close(watch_fd_); }

void ScMgr::start() {
    if (running_.exchange(true)) return;
    load_whitelist();
    // Watch the tool_dir for changes (inotify)
    watch_fd_ = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
    if (watch_fd_ >= 0)
        watch_wd_ = inotify_add_watch(watch_fd_, cfg_->tool_dir.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE);
    thread_ = std::thread(&ScMgr::run_loop, this);
    LOG(INFO, "sc: started (" << cfg_->tool_map.size() << " tools in manifest)");
}

void ScMgr::stop() {
    if (!running_.exchange(false)) return;
    if (thread_.joinable()) thread_.join();
    if (watch_wd_ >= 0) { inotify_rm_watch(watch_fd_, watch_wd_); watch_wd_ = -1; }
    if (watch_fd_ >= 0) { ::close(watch_fd_); watch_fd_ = -1; }
    LOG(INFO, "sc: stopped");
}

void ScMgr::run_loop() {
    while (running_.load()) {
        if (watch_fd_ >= 0) {
            char b[4096]; ssize_t n;
            while ((n = read(watch_fd_, b, sizeof(b))) > 0) {
                for (char* p = b; p < b + n; ) {
                    struct inotify_event* ev = reinterpret_cast<struct inotify_event*>(p);
                    if (ev->len > 0) {
                        std::string name(ev->name);
                        // Log with TOOL_ID if known
                        std::string tid = cfg_->tool_id_for(name);
                        if (ev->mask & IN_CREATE) LOG(INFO, "sc: [" << tid << "] created " << name);
                        if (ev->mask & IN_MODIFY) LOG(INFO, "sc: [" << tid << "] modified " << name);
                        if (ev->mask & IN_DELETE) LOG(WARN, "sc: [" << tid << "] DELETED " << name);
                    }
                    p += sizeof(struct inotify_event) + ev->len;
                }
            }
        }
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }
}

void ScMgr::load_whitelist() {
    std::lock_guard<std::mutex> lock(whitelist_mutex_);
    script_whitelist_.clear();

    // Primary source: engine.json tool_map (TOOL_ID → disguised filename)
    // Every filename in the tool_map is whitelisted for execution
    for (const auto& [tool_id, filename] : cfg_->tool_map) {
        script_whitelist_.insert(filename);
    }

    // Secondary source: static whitelist file (for node-specific additions)
    std::string p = cfg_->tool_dir + "/config/script_whitelist.txt";
    std::ifstream fs(p);
    if (fs.is_open()) {
        std::string line;
        while (std::getline(fs, line)) {
            line.erase(0, line.find_first_not_of(" \t\r\n"));
            line.erase(line.find_last_not_of(" \t\r\n") + 1);
            if (!line.empty() && line[0] != '#') script_whitelist_.insert(line);
        }
        fs.close();
    }
    LOG(INFO, "sc: whitelist loaded (" << script_whitelist_.size() << " entries from tool_map + static)");
}

std::pair<int, std::string> ScMgr::execute_script(const std::string& name, const std::vector<std::string>& args) {
    // Resolve: accept either TOOL_ID (e.g. "d4c2") or filename (e.g. "runtime-health.sh")
    std::string resolved_name = name;
    std::string tool_id = name;

    // If name looks like a TOOL_ID (4 hex chars), resolve to filename
    if (name.size() == 4 && name.find_first_not_of("0123456789abcdef") == std::string::npos) {
        std::string resolved_path = cfg_->resolve_tool(name);
        if (resolved_path.empty()) {
            LOG(WARN, "sc: unknown TOOL_ID: " << name);
            return {-1, "unknown tool_id"};
        }
        // Extract just filename from full path
        size_t slash = resolved_path.rfind('/');
        resolved_name = (slash != std::string::npos) ? resolved_path.substr(slash + 1) : resolved_path;
    } else {
        tool_id = cfg_->tool_id_for(name);
    }

    // Check whitelist
    {
        std::lock_guard<std::mutex> lock(whitelist_mutex_);
        if (script_whitelist_.find(resolved_name) == script_whitelist_.end()) {
            LOG(WARN, "sc: [" << tool_id << "] not whitelisted: " << resolved_name);
            return {-1, "not whitelisted"};
        }
    }

    std::string path = cfg_->tool_dir + "/" + resolved_name;
    struct stat st;
    if (stat(path.c_str(), &st) != 0 || !S_ISREG(st.st_mode)) {
        LOG(ERROR, "sc: [" << tool_id << "] not found: " << path);
        return {-1, "not found"};
    }

    // Validate arguments: reject shell metacharacters
    for (const auto& a : args) {
        if (a.find_first_of(";|&$`<>\\\"'") != std::string::npos) {
            LOG(WARN, "sc: [" << tool_id << "] bad chars in arg");
            return {-1, "bad arg"};
        }
    }

    // Build safe command via fork/exec
    LOG(INFO, "sc: [" << tool_id << "] exec " << resolved_name);

    int pipefd[2];
    if (pipe(pipefd) == -1) return {-1, "pipe failed"};

    pid_t pid = fork();
    if (pid == -1) { ::close(pipefd[0]); ::close(pipefd[1]); return {-1, "fork failed"}; }

    if (pid == 0) {
        // Child: redirect stdout+stderr to pipe
        ::close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        dup2(pipefd[1], STDERR_FILENO);
        ::close(pipefd[1]);

        // Build argv: [path, args..., NULL]
        std::vector<char*> exec_args;
        exec_args.push_back(const_cast<char*>(path.c_str()));
        for (const auto& a : args) exec_args.push_back(const_cast<char*>(a.c_str()));
        exec_args.push_back(nullptr);

        execvp(path.c_str(), exec_args.data());
        _exit(127); // exec failed
    }

    // Parent: read output
    ::close(pipefd[1]);
    std::ostringstream out;
    char b[1024]; size_t total = 0;
    ssize_t bytes;
    while ((bytes = read(pipefd[0], b, sizeof(b) - 1)) > 0) {
        b[bytes] = '\0';
        total += static_cast<size_t>(bytes);
        if (total > MAX_SCRIPT_OUTPUT) { out << "\n[TRUNCATED]"; break; }
        out << b;
    }
    ::close(pipefd[0]);

    int status = 0;
    waitpid(pid, &status, 0);
    int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;

    LOG(INFO, "sc: [" << tool_id << "] " << resolved_name << " exit=" << rc);
    return {rc, out.str()};
}

bool ScMgr::reload_scripts() {
    LOG(INFO, "sc: reload tool_map whitelist");
    load_whitelist();
    return true;
}

// ──────────────────────────────────────────────────────────────
// FwMgr — Firewall Manager (FIXED: execl/fork instead of system())
// ──────────────────────────────────────────────────────────────

FwMgr::FwMgr(EngineConfig* c) : cfg_(c) {}
FwMgr::~FwMgr() { stop(); }

void FwMgr::start() {
    if (running_.exchange(true)) return;
    thread_ = std::thread(&FwMgr::run_loop, this);
    LOG(INFO, "fw: started");
}

void FwMgr::stop() {
    if (!running_.exchange(false)) return;
    if (thread_.joinable()) thread_.join();
    LOG(INFO, "fw: stopped");
}

// Helper: execute iptables via fork/execv — no shell injection
static int exec_iptables(const std::vector<std::string>& argv) {
    pid_t pid = fork();
    if (pid == 0) {
        // Redirect stderr to /dev/null
        int devnull = open("/dev/null", O_WRONLY);
        if (devnull >= 0) { dup2(devnull, STDERR_FILENO); ::close(devnull); }
        // Build execv argument array
        std::vector<const char*> cargs;
        cargs.reserve(argv.size() + 1);
        for (const auto& a : argv) cargs.push_back(a.c_str());
        cargs.push_back(nullptr);
        execvp(cargs[0], const_cast<char* const*>(cargs.data()));
        _exit(127);
    } else if (pid > 0) {
        int status;
        waitpid(pid, &status, 0);
        return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
    }
    return -1;
}

void FwMgr::run_loop() {
    while (running_.load()) {
        {
            std::lock_guard<std::mutex> lock(blocks_mutex_);
            uint32_t now = now_epoch();
            auto it = active_blocks_.begin();
            while (it != active_blocks_.end()) {
                if (it->expire_at > 0 && it->expire_at < now) {
                    // Remove expired block via fork/execv (no shell)
                    exec_iptables({"iptables", "-D", "INPUT", "-s", it->ip, "-j", "DROP"});
                    it = active_blocks_.erase(it);
                } else ++it;
            }
        }
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }
}

bool FwMgr::apply_rule(const FirewallRule& r) {
    std::vector<std::string> argv = {"iptables", "-A", r.chain};
    if (!r.protocol.empty() && r.protocol != "all") { argv.push_back("-p"); argv.push_back(r.protocol); }
    if (!r.src.empty()) { argv.push_back("-s"); argv.push_back(r.src); }
    if (!r.dst.empty()) { argv.push_back("-d"); argv.push_back(r.dst); }
    if (r.dport > 0) { argv.push_back("--dport"); argv.push_back(std::to_string(r.dport)); }
    argv.push_back("-j"); argv.push_back(r.action);
    if (!r.comment.empty()) { argv.push_back("-m"); argv.push_back("comment"); argv.push_back("--comment"); argv.push_back(r.comment); }

    int rc = exec_iptables(argv);
    if (rc != 0) { LOG(WARN, "fw: iptables fail rc=" << rc); return false; }
    LOG(INFO, "fw: rule " << r.chain << " " << r.action << " " << r.protocol);
    return true;
}

bool FwMgr::apply_rules(const std::vector<FirewallRule>& rules) {
    bool ok = true;
    for (const auto& r : rules) if (!apply_rule(r)) ok = false;
    return ok;
}

bool FwMgr::clear_dynamic_rules() {
    // FIXED: fork/execv instead of system()
    exec_iptables({"iptables", "-F", "SE-CHAIN"});
    { std::lock_guard<std::mutex> lock(blocks_mutex_); active_blocks_.clear(); }
    LOG(INFO, "fw: cleared dynamic");
    return true;
}

bool FwMgr::block_ip(const std::string& ip, const std::string& reason, uint32_t expire_at) {
    struct in_addr addr;
    if (inet_pton(AF_INET, ip.c_str(), &addr) != 1) { LOG(WARN, "fw: bad IP " << ip); return false; }
    {
        std::lock_guard<std::mutex> lock(blocks_mutex_);
        for (const auto& b : active_blocks_) if (b.ip == ip) return true;
    }
    std::string comment_str = "NN:" + reason;
    int rc = exec_iptables({"iptables", "-I", "INPUT", "1", "-s", ip, "-j", "DROP",
                            "-m", "comment", "--comment", comment_str});
    if (rc != 0) { LOG(WARN, "fw: block fail rc=" << rc); return false; }
    { std::lock_guard<std::mutex> lock(blocks_mutex_); active_blocks_.push_back({ip, expire_at, reason}); }
    LOG(INFO, "fw: blocked " << ip << " " << reason);
    return true;
}

bool FwMgr::unblock_ip(const std::string& ip) {
    exec_iptables({"iptables", "-D", "INPUT", "-s", ip, "-j", "DROP"});
    { std::lock_guard<std::mutex> lock(blocks_mutex_); active_blocks_.erase(
        std::remove_if(active_blocks_.begin(), active_blocks_.end(), [&ip](const BlockEntry& b) { return b.ip == ip; }),
        active_blocks_.end()); }
    LOG(INFO, "fw: unblocked " << ip);
    return true;
}

// ──────────────────────────────────────────────────────────────
// LdMgr — Lockdown Manager (nlohmann json instead of jsoncpp)
// ──────────────────────────────────────────────────────────────

LdMgr::LdMgr(FwMgr* f, OriginBridge* b, EngineConfig* c) : fw_(f), bridge_(b), cfg_(c) {}
LdMgr::~LdMgr() { if (locked_down_.load()) exit_lockdown(); }

bool LdMgr::enter_lockdown(const std::string& reason) {
    if (locked_down_.exchange(true)) { LOG(WARN, "ld: already locked"); return false; }
    { std::lock_guard<std::mutex> lock(mutex_); lockdown_reason_ = reason; }
    LOG(FATAL, "ld: ENTER " << reason);
    if (fw_) {
        FirewallRule b_all; b_all.chain = "INPUT"; b_all.action = "DROP"; b_all.protocol = "all"; b_all.comment = "SE-LOCK:" + reason;
        FirewallRule a_lo; a_lo.chain = "INPUT"; a_lo.action = "ACCEPT"; a_lo.src = "127.0.0.1"; a_lo.comment = "SE-LOCK:lo";
        FirewallRule a_est; a_est.chain = "INPUT"; a_est.action = "ACCEPT"; a_est.comment = "SE-LOCK:est";
        FirewallRule a_wg; a_wg.chain = "INPUT"; a_wg.action = "ACCEPT"; a_wg.protocol = "udp"; a_wg.comment = "SE-LOCK:wg";
        fw_->apply_rule(a_lo);
        fw_->apply_rule(a_est);
        fw_->apply_rule(a_wg);
        fw_->apply_rule(b_all);
    }
    if (bridge_) {
        nlohmann::json r;
        r["event"] = "lockdown";
        r["reason"] = reason;
        r["node_id"] = cfg_->node_id;
        r["timestamp"] = now_epoch();
        std::string b = r.dump();
        bridge_->sign_request("POST", "/api/events/lockdown", b);
    }
    return true;
}

bool LdMgr::exit_lockdown() {
    if (!locked_down_.exchange(false)) { LOG(WARN, "ld: not locked"); return false; }
    LOG(INFO, "ld: EXIT");
    if (fw_) fw_->clear_dynamic_rules();
    { std::lock_guard<std::mutex> lock(mutex_); lockdown_reason_.clear(); }
    if (bridge_) {
        nlohmann::json r;
        r["event"] = "recover";
        r["node_id"] = cfg_->node_id;
        r["timestamp"] = now_epoch();
        std::string b = r.dump();
        bridge_->sign_request("POST", "/api/events/recover", b);
    }
    return true;
}
