#[path = "common/mod.rs"]
mod common;

#[cfg(target_os = "linux")]
use std::env;
#[cfg(target_os = "linux")]
use std::fs;
#[cfg(target_os = "linux")]
use std::path::Path;
#[cfg(target_os = "linux")]
use std::process::Command as StdCommand;
#[cfg(target_os = "linux")]
use std::thread;
#[cfg(target_os = "linux")]
use std::time::Duration;

#[cfg(target_os = "linux")]
use assert_cmd::Command;
#[cfg(target_os = "linux")]
use common::HomeEnvGuard;
#[cfg(target_os = "linux")]
use predicates::prelude::PredicateBooleanExt;
#[cfg(target_os = "linux")]
use systemg::{
    config::load_config,
    daemon::{Daemon, PidFile, ServiceLifecycleStatus, ServiceStateFile},
};
#[cfg(target_os = "linux")]
use tempfile::tempdir;

#[cfg(target_os = "linux")]
#[test]
fn logs_streams_when_pid_has_no_fds() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let config_path = dir.join("systemg.yaml");
    fs::write(
        &config_path,
        r#"
version: "1"
services:
  arb_rs:
    command: "/bin/sleep 30"
"#,
    )
    .expect("write config");

    let log_dir = home.join(".local/share/systemg/logs");
    fs::create_dir_all(&log_dir).expect("make log dir");
    let stdout_path = log_dir.join("arb_rs_stdout.log");
    let stderr_path = log_dir.join("arb_rs_stderr.log");
    fs::write(&stdout_path, "streamed stdout line\n").expect("write stdout log");
    fs::write(&stderr_path, "").expect("write stderr log");

    let mut pid_file = PidFile::load().expect("load pid file");
    pid_file.insert("arb_rs", 999_999).expect("insert pid");

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("sysg"));
    cmd.env("SYSTEMG_TAIL_MODE", "oneshot")
        .arg("logs")
        .arg("--config")
        .arg(&config_path)
        .arg("--service")
        .arg("arb_rs")
        .arg("--kind")
        .arg("stdout")
        .arg("--lines")
        .arg("1")
        .assert()
        .success()
        .stdout(predicates::str::contains("arb_rs"))
        .stdout(predicates::str::contains("streamed stdout line"));

    unsafe { env::remove_var("SYSTEMG_TAIL_MODE") };
}

#[cfg(target_os = "linux")]
#[test]
fn logs_sink_none_discards_service_output_without_files() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let config_path = dir.join("systemg.yaml");
    fs::write(
        &config_path,
        r#"
version: "1"
logs:
  sink: none
services:
  quiet:
    command: "sh -c 'echo stdout-line; echo stderr-line >&2; sleep 30'"
"#,
    )
    .expect("write config");

    let config =
        load_config(Some(config_path.to_string_lossy().as_ref())).expect("load config");
    let daemon = Daemon::from_config(config.clone(), false).expect("create daemon");
    let service = config.services.get("quiet").expect("quiet service");

    daemon
        .start_service("quiet", service)
        .expect("start quiet service");
    thread::sleep(Duration::from_millis(300));

    let log_dir = home.join(".local/share/systemg/logs");
    assert!(
        !log_dir.join("quiet_stdout.log").exists(),
        "stdout log should not be created when logs.sink is none"
    );
    assert!(
        !log_dir.join("quiet_stderr.log").exists(),
        "stderr log should not be created when logs.sink is none"
    );

    daemon.stop_service("quiet").expect("stop quiet service");
}

#[cfg(target_os = "linux")]
#[test]
fn logs_uses_snapshot_runtime_when_pid_file_is_missing_service() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let config_path = dir.join("systemg.yaml");
    fs::write(
        &config_path,
        r#"
version: "1"
services:
  demo:
    command: "/bin/sleep 30"
"#,
    )
    .expect("write config");

    let config =
        load_config(Some(config_path.to_string_lossy().as_ref())).expect("load config");
    let hash = config
        .services
        .get("demo")
        .expect("demo service")
        .compute_hash();

    let mut child = StdCommand::new("/bin/sleep")
        .arg("30")
        .spawn()
        .expect("spawn sleep");

    let mut state = ServiceStateFile::load().expect("load state");
    state
        .set(
            &hash,
            ServiceLifecycleStatus::Running,
            Some(child.id()),
            None,
            None,
        )
        .expect("persist state");

    let mut pid_file = PidFile::load().expect("load pid file");
    let _ = pid_file.remove("demo");

    let log_dir = home.join(".local/share/systemg/logs");
    fs::create_dir_all(&log_dir).expect("make log dir");
    fs::write(log_dir.join("demo_stdout.log"), "snapshot log line\n")
        .expect("write stdout log");

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("sysg"));
    cmd.env("SYSTEMG_TAIL_MODE", "oneshot")
        .arg("logs")
        .arg("--config")
        .arg(&config_path)
        .arg("--service")
        .arg("demo")
        .arg("--kind")
        .arg("stdout")
        .arg("--lines")
        .arg("1")
        .assert()
        .success()
        .stdout(predicates::str::contains(format!(
            "demo [pid {}]",
            child.id()
        )))
        .stdout(predicates::str::contains("snapshot log line"))
        .stdout(predicates::str::contains("offline").not());

    let _ = child.kill();
    let _ = child.wait();
    unsafe { env::remove_var("SYSTEMG_TAIL_MODE") };
}

#[cfg(target_os = "linux")]
#[test]
fn logs_lines_returns_last_lines_for_service() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let config_path = dir.join("systemg.yaml");
    fs::write(
        &config_path,
        r#"
version: "1"
services:
  demo:
    command: "/bin/sleep 30"
"#,
    )
    .expect("write config");

    let config =
        load_config(Some(config_path.to_string_lossy().as_ref())).expect("load config");
    let hash = config
        .services
        .get("demo")
        .expect("demo service")
        .compute_hash();

    let mut child = StdCommand::new("/bin/sleep")
        .arg("30")
        .spawn()
        .expect("spawn sleep");

    let mut state = ServiceStateFile::load().expect("load state");
    state
        .set(
            &hash,
            ServiceLifecycleStatus::Running,
            Some(child.id()),
            None,
            None,
        )
        .expect("persist state");

    let log_dir = home.join(".local/share/systemg/logs");
    fs::create_dir_all(&log_dir).expect("make log dir");
    fs::write(
        log_dir.join("demo_stdout.log"),
        "first log line\nsecond log line\nthird log line\nfourth log line\n",
    )
    .expect("write stdout log");

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("sysg"));
    let output = cmd
        .env("SYSTEMG_TAIL_MODE", "oneshot")
        .arg("logs")
        .arg("--config")
        .arg(&config_path)
        .arg("--service")
        .arg("demo")
        .arg("--kind")
        .arg("stdout")
        .arg("--lines")
        .arg("2")
        .output()
        .expect("run sysg logs");

    assert!(
        output.status.success(),
        "sysg logs should succeed, stderr was: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(!stdout.contains("first log line"));
    assert!(!stdout.contains("second log line"));
    assert!(stdout.contains("third log line"));
    assert!(stdout.contains("fourth log line"));

    let _ = child.kill();
    let _ = child.wait();
    unsafe { env::remove_var("SYSTEMG_TAIL_MODE") };
}

#[cfg(target_os = "linux")]
#[test]
fn logs_default_reads_combined_stdout_stderr_in_capture_order() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let config_path = dir.join("systemg.yaml");
    fs::write(
        &config_path,
        r#"
version: "1"
services:
  demo:
    command: "/bin/sleep 30"
"#,
    )
    .expect("write config");

    let config =
        load_config(Some(config_path.to_string_lossy().as_ref())).expect("load config");
    let hash = config
        .services
        .get("demo")
        .expect("demo service")
        .compute_hash();

    let mut child = StdCommand::new("/bin/sleep")
        .arg("30")
        .spawn()
        .expect("spawn sleep");

    let mut state = ServiceStateFile::load().expect("load state");
    state
        .set(
            &hash,
            ServiceLifecycleStatus::Running,
            Some(child.id()),
            None,
            None,
        )
        .expect("persist state");

    let log_dir = home.join(".local/share/systemg/logs");
    fs::create_dir_all(&log_dir).expect("make log dir");
    fs::write(
        log_dir.join("demo.log"),
        "2026-05-14T02:00:00.000000Z stdout first\n\
2026-05-14T02:00:00.000001Z stderr second\n\
2026-05-14T02:00:00.000002Z stdout third\n",
    )
    .expect("write combined log");
    fs::write(log_dir.join("demo_stdout.log"), "stdout-only\n")
        .expect("write stdout log");
    fs::write(log_dir.join("demo_stderr.log"), "stderr-only\n")
        .expect("write stderr log");

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("sysg"));
    let output = cmd
        .env("SYSTEMG_TAIL_MODE", "oneshot")
        .arg("logs")
        .arg("--config")
        .arg(&config_path)
        .arg("--service")
        .arg("demo")
        .arg("--lines")
        .arg("3")
        .output()
        .expect("run sysg logs");

    assert!(
        output.status.success(),
        "sysg logs should succeed, stderr was: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    let first_idx = stdout.find("stdout first").expect("first combined line");
    let second_idx = stdout.find("stderr second").expect("second combined line");
    let third_idx = stdout.find("stdout third").expect("third combined line");

    assert!(first_idx < second_idx);
    assert!(second_idx < third_idx);
    assert!(!stdout.contains("stdout-only"));
    assert!(!stdout.contains("stderr-only"));

    let _ = child.kill();
    let _ = child.wait();
    unsafe { env::remove_var("SYSTEMG_TAIL_MODE") };
}

#[cfg(target_os = "linux")]
#[test]
fn logs_without_service_groups_running_before_offline() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let config_path = dir.join("systemg.yaml");
    fs::write(
        &config_path,
        r#"
version: "1"
services:
  beta:
    command: "/bin/sleep 30"
  alpha:
    command: "/bin/echo alpha"
"#,
    )
    .expect("write config");

    let config =
        load_config(Some(config_path.to_string_lossy().as_ref())).expect("load config");
    let beta_hash = config
        .services
        .get("beta")
        .expect("beta service")
        .compute_hash();

    let mut child = StdCommand::new("/bin/sleep")
        .arg("30")
        .spawn()
        .expect("spawn sleep");

    let mut state = ServiceStateFile::load().expect("load state");
    state
        .set(
            &beta_hash,
            ServiceLifecycleStatus::Running,
            Some(child.id()),
            None,
            None,
        )
        .expect("persist state");

    let log_dir = home.join(".local/share/systemg/logs");
    fs::create_dir_all(&log_dir).expect("make log dir");
    fs::write(log_dir.join("beta_stdout.log"), "beta line\n").expect("write beta log");
    fs::write(log_dir.join("alpha_stdout.log"), "alpha line\n").expect("write alpha log");

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("sysg"));
    let output = cmd
        .env("SYSTEMG_TAIL_MODE", "oneshot")
        .arg("logs")
        .arg("--config")
        .arg(&config_path)
        .arg("--kind")
        .arg("stdout")
        .arg("--lines")
        .arg("1")
        .output()
        .expect("run sysg logs");

    assert!(
        output.status.success(),
        "sysg logs should succeed, stderr was: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    let running_idx = stdout.find("Running Services").expect("running section");
    let offline_idx = stdout.find("Offline Services").expect("offline section");
    let beta_idx = stdout.find("beta [pid ").expect("running service heading");
    let alpha_idx = stdout
        .find("alpha [offline]")
        .expect("offline service heading");

    assert!(
        running_idx < offline_idx,
        "running section should come first"
    );
    assert!(
        running_idx < beta_idx,
        "running service should appear in running section"
    );
    assert!(
        offline_idx < alpha_idx,
        "offline service should appear in offline section"
    );
    assert!(
        beta_idx < alpha_idx,
        "running service output should appear before offline service output"
    );
    assert!(stdout.contains("beta line"));
    assert!(stdout.contains("alpha line"));

    let _ = child.kill();
    let _ = child.wait();
    unsafe { env::remove_var("SYSTEMG_TAIL_MODE") };
}

#[cfg(target_os = "linux")]
fn read_log(path: &Path) -> String {
    fs::read_to_string(path).expect("read log file")
}

#[cfg(target_os = "linux")]
#[test]
fn logs_purge_truncates_only_selected_service() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let log_dir = home.join(".local/share/systemg/logs");
    fs::create_dir_all(&log_dir).expect("make log dir");

    let api_stdout = log_dir.join("api_stdout.log");
    let api_stderr = log_dir.join("api_stderr.log");
    let worker_stdout = log_dir.join("worker_stdout.log");
    let worker_stderr = log_dir.join("worker_stderr.log");

    fs::write(&api_stdout, "api stdout\n").expect("write api stdout");
    fs::write(&api_stderr, "api stderr\n").expect("write api stderr");
    fs::write(&worker_stdout, "worker stdout\n").expect("write worker stdout");
    fs::write(&worker_stderr, "worker stderr\n").expect("write worker stderr");

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("sysg"));
    cmd.arg("logs")
        .arg("--service")
        .arg("api")
        .arg("--purge")
        .assert()
        .success()
        .stdout(predicates::str::is_empty());

    assert_eq!(read_log(&api_stdout), "");
    assert_eq!(read_log(&api_stderr), "");
    assert_eq!(read_log(&worker_stdout), "worker stdout\n");
    assert_eq!(read_log(&worker_stderr), "worker stderr\n");
}

#[cfg(target_os = "linux")]
#[test]
fn logs_purge_without_service_truncates_all_logs() {
    let temp = tempdir().expect("failed to create tempdir");
    let dir = temp.path();
    let home = dir.join("home");
    fs::create_dir_all(&home).expect("failed to create home dir");
    let _home = HomeEnvGuard::set(&home);

    let log_dir = home.join(".local/share/systemg/logs");
    fs::create_dir_all(&log_dir).expect("make log dir");

    let api_stdout = log_dir.join("api_stdout.log");
    let api_stderr = log_dir.join("api_stderr.log");
    let supervisor = log_dir.join("supervisor.log");
    let spawn_log = log_dir.join("spawn/child_stdout.log");

    fs::write(&api_stdout, "api stdout\n").expect("write api stdout");
    fs::write(&api_stderr, "api stderr\n").expect("write api stderr");
    fs::write(&supervisor, "supervisor event\n").expect("write supervisor");
    fs::create_dir_all(spawn_log.parent().expect("spawn parent"))
        .expect("create spawn dir");
    fs::write(&spawn_log, "spawn output\n").expect("write spawn log");

    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("sysg"));
    cmd.arg("logs")
        .arg("--purge")
        .assert()
        .success()
        .stdout(predicates::str::is_empty());

    assert_eq!(read_log(&api_stdout), "");
    assert_eq!(read_log(&api_stderr), "");
    assert_eq!(read_log(&supervisor), "");
    assert_eq!(read_log(&spawn_log), "spawn output\n");
}
