use crate::common::{TestHooks, TestInfo, wait_for_stop_line};
use crate::{SIGNALS_APP, SLEEPER_APP, assert_no_proc, prepare_debugee_process};
use bugstalker::debugger::DebuggerBuilder;
use nix::sys::signal;
use nix::sys::signal::{SIGINT, SIGUSR1, SIGUSR2};
use serial_test::serial;
use std::thread;
use std::time::Duration;

#[test]
#[serial]
fn test_signal_stop_single_thread() {
    let process = prepare_debugee_process(SIGNALS_APP, &["single_thread"]);
    let debugee_pid = process.pid();
    let info = TestInfo::default();
    let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone()));
    let mut debugger = builder.build(process).unwrap();

    debugger.set_breakpoint_at_line("signals.rs", 12).unwrap();

    thread::spawn(move || {
        thread::sleep(Duration::from_secs(4));
        signal::kill(debugee_pid, SIGUSR1).unwrap();
    });

    debugger.start_debugee().unwrap();

    std::thread::sleep(Duration::from_secs(1));

    debugger.continue_debugee().unwrap();
    assert_eq!(info.line.take(), Some(12));

    debugger.continue_debugee().unwrap();

    assert_no_proc!(debugee_pid);
}

#[test]
#[serial]
fn test_signal_stop_multi_thread() {
    let process = prepare_debugee_process(SIGNALS_APP, &["multi_thread"]);
    let debugee_pid = process.pid();
    let info = TestInfo::default();
    let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone()));
    let mut debugger = builder.build(process).unwrap();

    debugger.set_breakpoint_at_line("signals.rs", 42).unwrap();

    thread::spawn(move || {
        thread::sleep(Duration::from_secs(4));
        signal::kill(debugee_pid, SIGUSR1).unwrap();
    });

    debugger.start_debugee().unwrap();
    std::thread::sleep(Duration::from_secs(1));

    debugger.continue_debugee().unwrap();
    assert_eq!(info.line.take(), Some(42));

    debugger.continue_debugee().unwrap();
    assert_no_proc!(debugee_pid);
}

#[test]
#[serial]
fn test_signal_stop_multi_thread_multiple_signal() {
    let process = prepare_debugee_process(SIGNALS_APP, &["multi_thread_multi_signal"]);
    let debugee_pid = process.pid();
    let info = TestInfo::default();
    let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone()));
    let mut debugger = builder.build(process).unwrap();

    debugger.set_breakpoint_at_line("signals.rs", 62).unwrap();

    thread::spawn(move || {
        thread::sleep(Duration::from_secs(4));
        signal::kill(debugee_pid, SIGUSR1).unwrap();
        signal::kill(debugee_pid, SIGUSR2).unwrap();
    });

    debugger.start_debugee().unwrap();

    std::thread::sleep(Duration::from_secs(1));

    debugger.continue_debugee().unwrap();
    debugger.continue_debugee().unwrap();

    assert_eq!(wait_for_stop_line(&info, 62, 5), 62);
    debugger.continue_debugee().unwrap();

    assert_no_proc!(debugee_pid);
}

#[test]
#[serial]
fn test_transparent_signals() {
    let process = prepare_debugee_process(SLEEPER_APP, &["-s", "1"]);
    let debugee_pid = process.pid();
    let info = TestInfo::default();
    let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone()));
    let mut debugger = builder.build(process).unwrap();

    thread::spawn(move || {
        thread::sleep(Duration::from_secs(2));
        signal::kill(debugee_pid, SIGINT).unwrap();
    });

    debugger.start_debugee().unwrap();

    // debugger takeover control here, just check that debugee still alive
    assert!(!debugger.thread_state().unwrap().is_empty());

    drop(debugger);
    assert_no_proc!(debugee_pid);
}
