#include "engine.hpp"
#include <getopt.h>
#include <csignal>
#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>

static std::atomic<Engine*> g_engine{nullptr};
static std::atomic<bool> g_shutdown{false};
static std::atomic<int> g_signal{0};
static std::string g_pid_file = E_PID;

static void signal_handler(int sig) {
    const char* name = "UNKNOWN";
    switch (sig) {
        case SIGTERM: name = "SIGTERM"; break;
        case SIGINT:  name = "SIGINT";  break;
        case SIGHUP:  name = "SIGHUP";  break;
        case SIGUSR1: name = "SIGUSR1"; break;
        case SIGUSR2: name = "SIGUSR2"; break;
    }
    char b[128];
    int n = snprintf(b, sizeof(b), "[INF] signal %s (%d)\n", name, sig);
    write(STDERR_FILENO, b, n);
    if (sig == SIGUSR1) {
        Engine* e = g_engine.load();
        if (e) LOG(INFO, "reload (SIGUSR1)");
        return;
    }
    if (sig == SIGUSR2) {
        Engine* e = g_engine.load();
        if (e) LOG(INFO, "status: running=" << e->is_running() << " state=" << state_name(e->state()) << " role=" << role_name(e->role()));
        return;
    }
    g_signal.store(sig);
    g_shutdown.store(true);
    Engine* e = g_engine.load();
    if (e) e->shutdown();
}

static void setup_signal_handlers() {
    struct sigaction sa;
    std::memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGTERM);
    sigaddset(&sa.sa_mask, SIGINT);
    sigaddset(&sa.sa_mask, SIGHUP);
    sigaddset(&sa.sa_mask, SIGUSR1);
    sigaddset(&sa.sa_mask, SIGUSR2);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGTERM, &sa, nullptr);
    sigaction(SIGINT, &sa, nullptr);
    sigaction(SIGHUP, &sa, nullptr);
    sigaction(SIGUSR1, &sa, nullptr);
    sigaction(SIGUSR2, &sa, nullptr);
    sa.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sa, nullptr);
    sigaction(SIGCHLD, &sa, nullptr);
}

static bool write_pid_file(const std::string& path) {
    std::string dir = path.substr(0, path.find_last_of('/'));
    if (!dir.empty()) mkdir(dir.c_str(), 0755);
    int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
    if (fd < 0) { std::cerr << "PID file: " << strerror(errno) << "\n"; return false; }
    std::string p = std::to_string(getpid());
    if (write(fd, p.c_str(), p.size()) < 0) { close(fd); return false; }
    close(fd);
    return true;
}

static bool remove_pid_file(const std::string& path) {
    return (unlink(path.c_str()) == 0);
}

static pid_t read_pid_file(const std::string& path) {
    std::ifstream fs(path);
    if (!fs.is_open()) return -1;
    pid_t pid; fs >> pid;
    return pid;
}

static bool daemonize() {
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); return false; }
    if (pid > 0) _exit(EXIT_SUCCESS);
    if (setsid() < 0) { perror("setsid"); return false; }
    pid = fork();
    if (pid < 0) { perror("fork"); return false; }
    if (pid > 0) _exit(EXIT_SUCCESS);
    if (chdir("/") < 0) { perror("chdir"); return false; }
    umask(0022);
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    int dn = open("/dev/null", O_RDWR);
    if (dn >= 0) {
        dup2(dn, STDIN_FILENO);
        dup2(dn, STDOUT_FILENO);
        dup2(dn, STDERR_FILENO);
        if (dn > STDERR_FILENO) close(dn);
    }
    return true;
}

static void print_usage(const char* prog) {
    std::cerr <<
        "sysd-engine v" << E_VERSION << "\n"
        "Usage: " << prog << " [options]\n"
        "\n"
        "Options:\n"
        "  -c, --config FILE      Config file (default: " << E_CFG << ")\n"
        "  -k, --key FILE         Master key file (default: " << E_KEYS "/master.key" << ")\n"
        "  -n, --node-id ID       Node identifier\n"
        "  -r, --role ROLE        Node role: proxy|filter|shield|origin|payment|email\n"
        "  -o, --origin URL       Origin dashboard URL\n"
        "  -f, --foreground       Run in foreground\n"
        "  -d, --drop-privs       Drop privileges after init\n"
        "  -u, --user USER        Run as user (default: sysd-engine)\n"
        "  -g, --group GROUP      Run as group (default: sysd-engine)\n"
        "  -p, --pid-file FILE    PID file (default: " << E_PID << ")\n"
        "  -l, --log-level LEVEL  Log level: DEBUG|INFO|WARN|ERROR|FATAL\n"
        "  -v, --version          Show version and exit\n"
        "  -h, --help             Show help and exit\n"
        "\n"
        "Signals:\n"
        "  SIGTERM, SIGINT, SIGHUP  Graceful shutdown\n"
        "  SIGUSR1                  Reload config\n"
        "  SIGUSR2                  Dump status\n"
    << std::endl;
}

struct CmdLineArgs {
    std::string config_file = E_CFG;
    std::string key_file;
    std::string node_id;
    std::string node_role_str = "shield";
    std::string origin_url;
    bool foreground = false;
    bool drop_privs = false;
    std::string run_user = "sysd-engine";
    std::string run_group = "sysd-engine";
    std::string pid_file = E_PID;
    std::string log_level = "INFO";
};

static bool parse_args(int argc, char** argv, CmdLineArgs& args) {
    static const struct option longopts[] = {
        {"config",      required_argument, nullptr, 'c'},
        {"key",         required_argument, nullptr, 'k'},
        {"node-id",     required_argument, nullptr, 'n'},
        {"role",        required_argument, nullptr, 'r'},
        {"origin",      required_argument, nullptr, 'o'},
        {"foreground",  no_argument,       nullptr, 'f'},
        {"drop-privs",  no_argument,       nullptr, 'd'},
        {"user",        required_argument, nullptr, 'u'},
        {"group",       required_argument, nullptr, 'g'},
        {"pid-file",    required_argument, nullptr, 'p'},
        {"log-level",   required_argument, nullptr, 'l'},
        {"version",     no_argument,       nullptr, 'v'},
        {"help",        no_argument,       nullptr, 'h'},
        {nullptr, 0, nullptr, 0}
    };
    int c;
    while ((c = getopt_long(argc, argv, "c:k:n:r:o:dfu:g:p:l:vh", longopts, nullptr)) != -1) {
        switch (c) {
            case 'c': args.config_file = optarg; break;
            case 'k': args.key_file = optarg; break;
            case 'n': args.node_id = optarg; break;
            case 'r': args.node_role_str = optarg; break;
            case 'o': args.origin_url = optarg; break;
            case 'f': args.foreground = true; break;
            case 'd': args.drop_privs = true; break;
            case 'u': args.run_user = optarg; break;
            case 'g': args.run_group = optarg; break;
            case 'p': args.pid_file = optarg; break;
            case 'l': args.log_level = optarg; break;
            case 'v': std::cout << "sysd-engine v" << E_VERSION << std::endl; exit(EXIT_SUCCESS);
            case 'h': print_usage(argv[0]); exit(EXIT_SUCCESS);
            default: print_usage(argv[0]); return false;
        }
    }
    if (args.node_id.empty()) {
        std::ifstream fs("/etc/machine-id");
        if (fs.is_open()) { std::getline(fs, args.node_id); fs.close(); }
        if (args.node_id.empty()) {
            char h[256];
            if (gethostname(h, sizeof(h)) == 0) args.node_id = h;
        }
    }
    if (args.node_id.empty()) {
        std::cerr << "Error: node-id required\n";
        return false;
    }
    return true;
}

static NodeRole parse_role(const std::string& s) {
    if (s == "a")   return NodeRole::A;
    if (s == "b")  return NodeRole::B;
    if (s == "c")  return NodeRole::C;
    if (s == "d")  return NodeRole::D;
    if (s == "e") return NodeRole::E;
    if (s == "f") return NodeRole::F;
    return NodeRole::G;
}

int main(int argc, char** argv) {
    CmdLineArgs args;
    if (!parse_args(argc, argv, args)) return EXIT_FAILURE;
    {
        LogLevel lvl = LogLevel::INFO;
        if (args.log_level == "DEBUG") lvl = LogLevel::DEBUG;
        else if (args.log_level == "WARN")  lvl = LogLevel::WARN;
        else if (args.log_level == "ERROR") lvl = LogLevel::ERROR;
        else if (args.log_level == "FATAL") lvl = LogLevel::FATAL;
        Logger::set_level(lvl);
    }
    pid_t existing = read_pid_file(args.pid_file);
    if (existing > 0 && kill(existing, 0) == 0) {
        std::cerr << "Error: already running (PID " << existing << ")\n";
        return EXIT_FAILURE;
    }
    if (!args.foreground) {
        if (!daemonize()) { std::cerr << "Error: daemonize failed\n"; return EXIT_FAILURE; }
    }
    disguise_process(E_FAKE);
    g_pid_file = args.pid_file;
    if (!write_pid_file(g_pid_file)) LOG(WARN, "PID write failed");
    setup_signal_handlers();
    LOG(INFO, "=== sysd-engine v" << E_VERSION << " ===");
    LOG(INFO, "node=" << args.node_id << " role=" << args.node_role_str << " pid=" << getpid());
    EngineConfig cfg;
    cfg.node_id = args.node_id;
    cfg.node_role = parse_role(args.node_role_str);
    cfg.config_file = args.config_file;
    cfg.key_file = args.key_file.empty() ? std::string(E_KEYS) + "/master.key" : args.key_file;
    cfg.origin_url = args.origin_url.empty() ? "http://127.0.0.1:7171" : args.origin_url;
    cfg.drop_privileges = args.drop_privs;
    cfg.run_user = args.run_user;
    cfg.run_group = args.run_group;
    char h[256];
    if (gethostname(h, sizeof(h)) == 0) cfg.hostname = h;
    Engine* engine = nullptr;
    try {
        engine = new Engine(cfg);
        g_engine.store(engine);
        engine->disguise();
        if (args.drop_privs) {
            if (!engine->drop_privileges()) {
                LOG(FATAL, "priv drop failed");
                delete engine;
                remove_pid_file(g_pid_file);
                return EXIT_FAILURE;
            }
        }
        engine->run();
    } catch (const std::exception& e) {
        LOG(FATAL, "fatal: " << e.what());
        if (engine) delete engine;
        remove_pid_file(g_pid_file);
        return EXIT_FAILURE;
    }
    LOG(INFO, "shutdown sig=" << g_signal.load());
    g_engine.store(nullptr);
    delete engine;
    remove_pid_file(g_pid_file);
    LOG(INFO, "exit clean");
    return EXIT_SUCCESS;
}
