// escuta — loop simples de mensagens controlado por comandos kk
//
// Uso direto:
//   ./escuta                 roda em primeiro plano com intervalo padrão (1000 ms)
//   ./escuta --intervalo N   roda em primeiro plano com intervalo N ms
//   ./escuta --daemon        inicia/altera execução em segundo plano
//   ./escuta --desligar      para execução em segundo plano

#include <algorithm>
#include <atomic>
#include <cerrno>
#include <chrono>
#include <cctype>
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <limits.h>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <thread>
#include <ctime>
#include <unistd.h>
#include <fcntl.h>
#include <vector>

namespace fs = std::filesystem;

static constexpr long DEFAULT_INTERVAL_MS = 1000;
static std::atomic<bool> running{true};
static bool foreground_output = true;

struct CycleResult {
    int roteadas = 0;
    int acionadas = 0;
    int sem_comando = 0;
};

static void handle_signal(int) {
    running = false;
}

static std::string self_exe_path() {
    char buf[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
    if (n < 0) return "";
    buf[n] = '\0';
    return std::string(buf);
}

static fs::path escuta_dir() {
    const std::string self = self_exe_path();
    if (self.empty()) return fs::current_path();
    return fs::path(self).parent_path();
}

static fs::path project_root() {
    return escuta_dir().parent_path();
}

static fs::path runtime_dir() {
    return escuta_dir() / ".runtime";
}

static fs::path espaco_dir() {
    return escuta_dir() / "espaco";
}

static fs::path cx_entrada_dir() {
    return espaco_dir() / "cx-entrada";
}

static fs::path caixas_dir() {
    return espaco_dir() / "caixas";
}

static fs::path processando_dir() {
    return espaco_dir() / "processando";
}

static fs::path concluidas_dir() {
    return espaco_dir() / "concluidas";
}

static fs::path erro_dir() {
    return espaco_dir() / "erro";
}

static fs::path pid_file() {
    return runtime_dir() / "escuta.pid";
}

static fs::path interval_file() {
    return runtime_dir() / "intervalo.ms";
}

static fs::path stop_file() {
    return runtime_dir() / "parar";
}

static fs::path log_file() {
    return runtime_dir() / "escuta.log";
}

static bool parse_positive_long(const std::string& s, long& value) {
    if (s.empty()) return false;
    char* end = nullptr;
    errno = 0;
    long parsed = std::strtol(s.c_str(), &end, 10);
    if (errno != 0 || end == s.c_str() || *end != '\0' || parsed <= 0) return false;
    value = parsed;
    return true;
}

static long read_interval() {
    std::ifstream in(interval_file());
    std::string value;
    long interval = DEFAULT_INTERVAL_MS;
    if (in >> value && parse_positive_long(value, interval)) return interval;
    return DEFAULT_INTERVAL_MS;
}

static bool write_interval(long interval) {
    std::error_code ec;
    fs::create_directories(runtime_dir(), ec);
    std::ofstream out(interval_file(), std::ios::trunc);
    if (!out) return false;
    out << interval << "\n";
    return true;
}

static pid_t read_pid() {
    std::ifstream in(pid_file());
    long pid = 0;
    if (!(in >> pid) || pid <= 0) return 0;
    return static_cast<pid_t>(pid);
}

static bool process_alive(pid_t pid) {
    if (pid <= 0) return false;
    if (kill(pid, 0) == 0) return true;
    return errno == EPERM;
}

static void remove_file(const fs::path& path) {
    std::error_code ec;
    fs::remove(path, ec);
}

static std::string now_string() {
    auto now = std::chrono::system_clock::now();
    std::time_t t = std::chrono::system_clock::to_time_t(now);
    char buf[64];
    std::tm tm{};
    localtime_r(&t, &tm);
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
    return std::string(buf);
}

static void append_log(const std::string& line) {
    std::error_code ec;
    fs::create_directories(runtime_dir(), ec);
    std::ofstream out(log_file(), std::ios::app);
    if (out) out << line << "\n";
}

static void emit_log(const std::string& line) {
    append_log(line);
    if (foreground_output) std::cout << line << std::endl;
}

static void ensure_espaco_layout() {
    std::error_code ec;
    fs::create_directories(cx_entrada_dir(), ec);
    fs::create_directories(caixas_dir(), ec);
    fs::create_directories(processando_dir(), ec);
    fs::create_directories(concluidas_dir(), ec);
    fs::create_directories(erro_dir(), ec);
}

static std::string read_text(const fs::path& path) {
    std::ifstream in(path);
    std::ostringstream ss;
    ss << in.rdbuf();
    return ss.str();
}

static std::string trim_copy(std::string s) {
    auto not_space = [](unsigned char c) { return !std::isspace(c); };
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));
    s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
    return s;
}

static std::string json_string_value(const std::string& json, const std::string& key) {
    const std::string quoted = "\"" + key + "\"";
    std::size_t pos = 0;
    while ((pos = json.find(quoted, pos)) != std::string::npos) {
        std::size_t colon = json.find(':', pos + quoted.size());
        if (colon == std::string::npos) return "";
        std::size_t i = colon + 1;
        while (i < json.size() && std::isspace(static_cast<unsigned char>(json[i]))) ++i;
        if (i >= json.size()) return "";
        if (json[i] == '"') {
            ++i;
            std::string out;
            bool esc = false;
            for (; i < json.size(); ++i) {
                char c = json[i];
                if (esc) {
                    out.push_back(c);
                    esc = false;
                } else if (c == '\\') {
                    esc = true;
                } else if (c == '"') {
                    return out;
                } else {
                    out.push_back(c);
                }
            }
            return "";
        }
        std::size_t end = i;
        while (end < json.size() && json[end] != ',' && json[end] != '}' && json[end] != '\n' && json[end] != '\r') ++end;
        return trim_copy(json.substr(i, end - i));
    }
    return "";
}

static std::string safe_segment(const std::string& in) {
    std::string out;
    for (unsigned char c : in) {
        if (std::isalnum(c) || c == '.' || c == '_' || c == '-') out.push_back(static_cast<char>(c));
        else out.push_back('-');
    }
    return out.empty() ? "destino" : out;
}

static fs::path caixa_destino_dir(const std::string& destino) {
    fs::path out = caixas_dir();
    std::stringstream ss(destino);
    std::string part;
    while (std::getline(ss, part, '/')) {
        if (!part.empty() && part != "." && part != "..") out /= safe_segment(part);
    }
    std::error_code ec;
    fs::create_directories(out, ec);
    return out;
}

static void write_flag(const fs::path& dir) {
    std::error_code ec;
    fs::create_directories(dir, ec);
    std::ofstream out(dir / "tem-mensagem.flag", std::ios::trunc);
    if (out) out << now_string() << "\n";
}

static bool dir_has_json(const fs::path& dir) {
    std::error_code ec;
    if (!fs::is_directory(dir, ec)) return false;
    for (const auto& e : fs::directory_iterator(dir, ec)) {
        if (ec) break;
        if (e.is_regular_file(ec) && e.path().extension() == ".json") return true;
    }
    return false;
}

static std::string timestamp_key(const fs::path& path) {
    const std::string txt = read_text(path);
    std::string ts = json_string_value(txt, "timestamp");
    if (ts.empty()) ts = json_string_value(txt, "criadoEm");
    if (!ts.empty()) return ts;
    return path.filename().string();
}

static std::vector<fs::path> list_json_files(const fs::path& dir, bool recursive) {
    std::vector<fs::path> files;
    std::error_code ec;
    if (!fs::is_directory(dir, ec)) return files;

    if (recursive) {
        for (const auto& e : fs::recursive_directory_iterator(dir, ec)) {
            if (ec) break;
            if (e.is_regular_file(ec) && e.path().extension() == ".json" && e.path().filename().string().find(".tmp") == std::string::npos) {
                files.push_back(e.path());
            }
        }
    } else {
        for (const auto& e : fs::directory_iterator(dir, ec)) {
            if (ec) break;
            if (e.is_regular_file(ec) && e.path().extension() == ".json" && e.path().filename().string().find(".tmp") == std::string::npos) {
                files.push_back(e.path());
            }
        }
    }

    std::sort(files.begin(), files.end(), [](const fs::path& a, const fs::path& b) {
        const std::string ta = timestamp_key(a);
        const std::string tb = timestamp_key(b);
        if (ta != tb) return ta < tb;
        return a.filename().string() < b.filename().string();
    });
    return files;
}

static fs::path unique_path(const fs::path& wanted) {
    if (!fs::exists(wanted)) return wanted;
    const fs::path dir = wanted.parent_path();
    const std::string stem = wanted.stem().string();
    const std::string ext = wanted.extension().string();
    for (int i = 1; i < 10000; ++i) {
        fs::path p = dir / (stem + "-" + std::to_string(i) + ext);
        if (!fs::exists(p)) return p;
    }
    return dir / (stem + "-" + std::to_string(getpid()) + ext);
}

static fs::path claim_path(const std::string& prefix, const fs::path& src) {
    return processando_dir() / (prefix + "-" + std::to_string(std::time(nullptr)) + "-" + std::to_string(getpid()) + "-" + src.filename().string());
}

static bool rename_file(const fs::path& src, const fs::path& dst) {
    std::error_code ec;
    fs::create_directories(dst.parent_path(), ec);
    fs::rename(src, dst, ec);
    return !ec;
}

static bool move_to_dir(const fs::path& src, const fs::path& dir) {
    std::error_code ec;
    fs::create_directories(dir, ec);
    return rename_file(src, unique_path(dir / src.filename()));
}

static std::string destino_da_mensagem(const std::string& txt) {
    std::string destino = json_string_value(txt, "destino");
    if (destino.empty()) destino = json_string_value(txt, "destinatario");
    return destino;
}

static bool process_one_entrada() {
    const auto files = list_json_files(cx_entrada_dir(), false);
    for (const auto& file : files) {
        const fs::path claim = claim_path("entrada", file);
        if (!rename_file(file, claim)) continue;

        const std::string txt = read_text(claim);
        const std::string destino = destino_da_mensagem(txt);
        if (destino.empty()) {
            move_to_dir(claim, erro_dir());
            emit_log("escuta: mensagem sem destino movida para erro | arquivo=" + file.filename().string());
            return true;
        }

        const fs::path cdir = caixa_destino_dir(destino);
        const fs::path final = unique_path(cdir / file.filename());
        if (!rename_file(claim, final)) {
            move_to_dir(claim, erro_dir());
            emit_log("escuta: falha ao entregar mensagem | arquivo=" + file.filename().string());
            return true;
        }

        write_flag(cdir);
        emit_log("escuta: mensagem entregue | destino=" + destino + " | arquivo=" + final.string());
        return true;
    }
    return false;
}

static bool executable_file(const fs::path& path) {
    std::error_code ec;
    return fs::is_regular_file(path, ec) && access(path.c_str(), X_OK) == 0;
}

static fs::path find_destination_command(const std::string& destino) {
    std::vector<fs::path> candidates;
    candidates.push_back(project_root() / destino);
    candidates.push_back(escuta_dir() / destino);

    std::string last = destino;
    std::size_t slash = last.find_last_of('/');
    if (slash != std::string::npos) last = last.substr(slash + 1);
    if (!last.empty()) {
        candidates.push_back(project_root() / last);
        candidates.push_back(escuta_dir() / last);
        candidates.push_back(project_root() / "agentes" / last);
        candidates.push_back(project_root() / "conectores" / last);
    }

    for (const auto& c : candidates) {
        if (executable_file(c)) return c;
    }
    return {};
}

static int run_destination(const fs::path& command, const fs::path& message) {
    pid_t pid = fork();
    if (pid < 0) return 127;
    if (pid == 0) {
        std::string cmd = command.string();
        std::string arg0 = command.filename().string();
        std::string flag = "--ler-mensagem";
        std::string msg = message.string();
        char* argv[] = {
            const_cast<char*>(arg0.c_str()),
            const_cast<char*>(flag.c_str()),
            const_cast<char*>(msg.c_str()),
            nullptr
        };
        chdir(project_root().c_str());
        execv(cmd.c_str(), argv);
        _exit(127);
    }

    int status = 0;
    if (waitpid(pid, &status, 0) < 0) return 127;
    if (WIFEXITED(status)) return WEXITSTATUS(status);
    if (WIFSIGNALED(status)) return 128 + WTERMSIG(status);
    return 1;
}

static bool process_one_caixa(bool& sem_comando) {
    sem_comando = false;
    const auto files = list_json_files(caixas_dir(), true);
    for (const auto& file : files) {
        const std::string txt = read_text(file);
        const std::string destino = destino_da_mensagem(txt);
        if (destino.empty()) continue;

        const fs::path command = find_destination_command(destino);
        if (command.empty()) {
            write_flag(file.parent_path());
            sem_comando = true;
            continue;
        }

        const fs::path origem_dir = file.parent_path();
        const fs::path claim = claim_path("acao", file);
        if (!rename_file(file, claim)) continue;
        if (!dir_has_json(origem_dir)) remove_file(origem_dir / "tem-mensagem.flag");

        emit_log("escuta: acionando destino | destino=" + destino + " | comando=" + command.string() + " | arquivo=" + claim.string());
        const int rc = run_destination(command, claim);
        if (rc == 0) {
            move_to_dir(claim, concluidas_dir());
            emit_log("escuta: mensagem concluída | destino=" + destino + " | codigo=0");
        } else {
            move_to_dir(claim, erro_dir());
            emit_log("escuta: mensagem com erro | destino=" + destino + " | codigo=" + std::to_string(rc));
        }
        return true;
    }
    return false;
}

static CycleResult process_cycle() {
    ensure_espaco_layout();
    CycleResult r;
    if (process_one_entrada()) r.roteadas = 1;
    bool sem_comando = false;
    if (process_one_caixa(sem_comando)) r.acionadas = 1;
    if (sem_comando) r.sem_comando = 1;
    return r;
}

static void run_loop() {
    std::error_code ec;
    fs::create_directories(runtime_dir(), ec);
    ensure_espaco_layout();
    remove_file(stop_file());

    {
        std::ofstream out(pid_file(), std::ios::trunc);
        out << getpid() << "\n";
    }

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

    emit_log("escuta: iniciado | pid=" + std::to_string(getpid()) +
             " | intervalo=" + std::to_string(read_interval()) + "ms" +
             " | " + now_string());

    unsigned long long count = 0;
    auto last_loop = std::chrono::steady_clock::now();

    while (running) {
        if (fs::exists(stop_file(), ec)) break;

        const long interval = read_interval();
        const auto now = std::chrono::steady_clock::now();
        const long elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_loop).count();

        if (elapsed >= interval) {
            ++count;
            last_loop = now;
            CycleResult r = process_cycle();
            emit_log("escuta: loop " + std::to_string(count) +
                     " | intervalo=" + std::to_string(interval) + "ms" +
                     " | roteadas=" + std::to_string(r.roteadas) +
                     " | acionadas=" + std::to_string(r.acionadas) +
                     " | sem_comando=" + std::to_string(r.sem_comando) +
                     " | " + now_string());
            continue;
        }

        const long chunk = std::min<long>(100, interval - elapsed);
        std::this_thread::sleep_for(std::chrono::milliseconds(chunk));
    }

    remove_file(stop_file());
    remove_file(pid_file());
    emit_log("escuta: parado | " + now_string());
}

static int start_daemon(long interval) {
    if (!write_interval(interval)) {
        std::cerr << "escuta: não foi possível gravar intervalo\n";
        return 1;
    }

    const pid_t old_pid = read_pid();
    if (process_alive(old_pid)) {
        append_log("escuta: intervalo ajustado para " + std::to_string(interval) + "ms | " + now_string());
        std::cout << "escuta: já em execução (pid " << old_pid
                  << "); intervalo ajustado para " << interval << "ms\n"
                  << "escuta: log em " << log_file().string() << "\n";
        return 0;
    }
    remove_file(pid_file());
    remove_file(stop_file());
    append_log("escuta: iniciando com intervalo=" + std::to_string(interval) + "ms | " + now_string());

    pid_t pid = fork();
    if (pid < 0) {
        std::perror("escuta: fork");
        return 1;
    }
    if (pid > 0) {
        std::cout << "escuta: iniciado (pid " << pid << ", intervalo " << interval << "ms)\n"
                  << "escuta: log em " << log_file().string() << "\n";
        return 0;
    }

    if (setsid() < 0) _exit(1);
    umask(0);

    int devnull = open("/dev/null", O_RDONLY);
    if (devnull >= 0) {
        dup2(devnull, STDIN_FILENO);
        close(devnull);
    }

    int logfd = open(log_file().c_str(), O_CREAT | O_WRONLY | O_APPEND, 0644);
    if (logfd >= 0) {
        dup2(logfd, STDOUT_FILENO);
        dup2(logfd, STDERR_FILENO);
        close(logfd);
    }

    foreground_output = false;
    run_loop();
    _exit(0);
}

static int stop_daemon() {
    const pid_t pid = read_pid();
    if (!process_alive(pid)) {
        remove_file(pid_file());
        remove_file(stop_file());
        std::cout << "escuta: não estava em execução\n";
        return 0;
    }

    std::error_code ec;
    fs::create_directories(runtime_dir(), ec);
    {
        std::ofstream out(stop_file(), std::ios::trunc);
        out << "parar\n";
    }
    kill(pid, SIGTERM);

    for (int i = 0; i < 50; ++i) {
        if (!process_alive(pid)) break;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    if (process_alive(pid)) {
        std::cerr << "escuta: pedido de parada enviado, mas processo ainda existe (pid " << pid << ")\n";
        return 1;
    }

    remove_file(pid_file());
    remove_file(stop_file());
    append_log("escuta: desligado | " + now_string());
    std::cout << "escuta: desligado\n";
    return 0;
}

static void usage() {
    std::cout << "uso: escuta [--daemon] [--intervalo <ms>] [--desligar]\n";
}

int main(int argc, char** argv) {
    bool daemon = false;
    bool desligar = false;
    long interval = DEFAULT_INTERVAL_MS;

    for (int i = 1; i < argc; ++i) {
        const std::string arg = argv[i];
        if (arg == "--daemon") {
            daemon = true;
        } else if (arg == "--desligar") {
            desligar = true;
        } else if (arg == "--intervalo" && i + 1 < argc) {
            if (!parse_positive_long(argv[++i], interval)) {
                std::cerr << "escuta: intervalo inválido\n";
                return 2;
            }
        } else if (arg.rfind("--intervalo=", 0) == 0) {
            if (!parse_positive_long(arg.substr(12), interval)) {
                std::cerr << "escuta: intervalo inválido\n";
                return 2;
            }
        } else if (arg == "--help" || arg == "-h") {
            usage();
            return 0;
        } else {
            std::cerr << "escuta: argumento desconhecido: " << arg << "\n";
            usage();
            return 2;
        }
    }

    if (desligar) return stop_daemon();
    if (daemon) return start_daemon(interval);

    if (!write_interval(interval)) {
        std::cerr << "escuta: não foi possível gravar intervalo\n";
        return 1;
    }
    run_loop();
    return 0;
}
