/*******************************************************************************
 *
 *    Copyright (c) 2025 - 2026 Haixing Hu.
 *
 *    SPDX-License-Identifier: Apache-2.0
 *
 *    Licensed under the Apache License, Version 2.0.
 *
 ******************************************************************************/
//! Tests for [`DelayExecutor`](qubit_executor::executor::DelayExecutor).

use std::{
    io,
    sync::Arc,
    sync::mpsc,
    thread,
    time::{
        Duration,
        Instant,
    },
};

use qubit_executor::{
    CancelResult,
    TaskExecutionError,
    executor::{
        DelayExecutor,
        Executor,
    },
    hook::NoopTaskHook,
    service::SubmissionError,
};

fn delayed_value_task() -> Result<usize, io::Error> {
    Ok(42)
}

#[test]
fn test_delay_executor_delays_task_start() {
    let executor = DelayExecutor::new(Duration::from_millis(80));
    let (started_tx, started_rx) = mpsc::channel();
    let start = Instant::now();

    assert_eq!(executor.delay(), Duration::from_millis(80));
    let handle = executor
        .execute(move || {
            started_tx
                .send(Instant::now())
                .expect("test should receive start time");
            Ok::<(), io::Error>(())
        })
        .expect("worker thread should spawn");

    assert!(
        started_rx.recv_timeout(Duration::from_millis(30)).is_err(),
        "task should not start before the configured delay",
    );
    let started_at = started_rx
        .recv_timeout(Duration::from_secs(1))
        .expect("task should start after delay");
    handle.get().expect("delayed task should complete");
    assert!(started_at.duration_since(start) >= Duration::from_millis(70));
}

#[test]
fn test_delay_executor_returns_callable_value() {
    let executor = DelayExecutor::new(Duration::ZERO).with_hook(Arc::new(NoopTaskHook));

    let handle = executor
        .call(delayed_value_task as fn() -> Result<usize, io::Error>)
        .expect("worker thread should spawn");

    assert_eq!(handle.get().expect("callable should complete"), 42);
}

#[test]
fn test_delay_executor_cancel_before_start_skips_callable() {
    let executor = DelayExecutor::new(Duration::from_millis(80));

    let handle = executor
        .call(delayed_value_task as fn() -> Result<usize, io::Error>)
        .expect("worker thread should spawn");

    assert_eq!(handle.cancel(), CancelResult::Cancelled);
    assert!(matches!(handle.get(), Err(TaskExecutionError::Cancelled)));
    thread::sleep(Duration::from_millis(120));
}

#[test]
fn test_delay_executor_reports_worker_spawn_failure() {
    let executor = DelayExecutor::new(Duration::ZERO)
        .with_hook(Arc::new(NoopTaskHook))
        .with_stack_size(usize::MAX);

    let result = executor.call(delayed_value_task as fn() -> Result<usize, io::Error>);

    assert!(matches!(
        result,
        Err(SubmissionError::WorkerSpawnFailed { .. }),
    ));
}
