// test_terminal_panel_smoke.cpp — smoke test para terminal_panel.
//
// Prueba real del PTY en Linux: spawn "echo hello && exit 0",
// espera output, verifica que el scrollback contiene "hello".
//
// En Windows: test skipped (ConPTY require DISPLAY y proceso vivo — CI).
// En Linux sin forkpty: verifica que el build es correcto al menos.

#define CATCH_CONFIG_MAIN
#include "catch_amalgamated.hpp"

#include "viz/terminal_panel/terminal_panel.h"

#include <chrono>
#include <string>
#include <thread>

#ifdef _WIN32
// En Windows en CI, skipeamos el smoke del proceso real.
TEST_CASE("smoke: spawn echo hello and exit, scrollback contains hello", "[terminal_panel][smoke]") {
    SKIP("Smoke PTY test skipped on Windows CI");
}
#else

// Helper: concatena todas las celdas del scrollback como texto plano.
static std::string scrollback_text(fn_term::TerminalPanel& p) {
    std::lock_guard<std::mutex> lk(p.buf_mutex);
    std::string result;
    for (const auto& line : p.lines) {
        for (const auto& cell : line) {
            if (cell.ch >= 0x20 && cell.ch < 0x7F)
                result += static_cast<char>(cell.ch);
        }
        result += '\n';
    }
    return result;
}

TEST_CASE("smoke: spawn echo hello and exit, scrollback contains hello", "[terminal_panel][smoke]") {
    fn_term::TerminalPanel term;
    term.shell = "/bin/bash";
    term.scrollback_lines = 100;

    fn_term::open(term);
    REQUIRE(term.is_open());

    // Enviar el comando y esperar a que el proceso salga.
    fn_term::send(term, "echo hello && exit 0\n");

    // Esperar máximo 2 segundos a que el proceso termine.
    auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
    while (!term.process_exited.load()
           && std::chrono::steady_clock::now() < deadline) {
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
    }

    // Dar 100ms adicionales para que el reader thread procese el último output.
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    std::string text = scrollback_text(term);
    fn_term::close(term);

    INFO("scrollback: " << text);
    REQUIRE(text.find("hello") != std::string::npos);
}

TEST_CASE("smoke: process exits cleanly", "[terminal_panel][smoke]") {
    fn_term::TerminalPanel term;
    term.shell = "/bin/bash";
    term.scrollback_lines = 50;

    fn_term::open(term);
    REQUIRE(term.is_open());

    fn_term::send(term, "exit 0\n");

    auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
    while (!term.process_exited.load()
           && std::chrono::steady_clock::now() < deadline) {
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
    }

    REQUIRE(term.process_exited.load());
    REQUIRE(term.exit_code == 0);

    fn_term::close(term);
}

TEST_CASE("smoke: readonly panel ignores send", "[terminal_panel][smoke]") {
    fn_term::TerminalPanel term;
    term.shell    = "/bin/bash";
    term.readonly = true;
    term.scrollback_lines = 50;

    fn_term::open(term);
    REQUIRE(term.is_open());

    // send() no debe hacer nada (readonly).
    fn_term::send(term, "echo should_not_appear\n");
    std::this_thread::sleep_for(std::chrono::milliseconds(200));

    std::string text = scrollback_text(term);
    fn_term::close(term);

    // "should_not_appear" no debería estar en el scrollback porque send es no-op.
    INFO("scrollback: " << text);
    REQUIRE(text.find("should_not_appear") == std::string::npos);
}

#endif // !_WIN32
