/*******************************************************************************
 *
 *    Copyright (c) 2025 - 2026 Haixing Hu.
 *
 *    SPDX-License-Identifier: Apache-2.0
 *
 *    Licensed under the Apache License, Version 2.0.
 *
 ******************************************************************************/
//! Configurable runnable tasks for executor tests.

use std::{
    panic::panic_any,
    thread,
    time::Duration,
};

use qubit_atomic::ArcAtomicCount;
use qubit_function::Runnable;

/// Test task behavior used to keep executor coverage in one monomorphization.
#[derive(Debug, Clone)]
pub enum TestTaskAction {
    /// Complete successfully without extra side effects.
    Succeed,
    /// Increment the supplied counter, then succeed.
    CountSuccess {
        /// Counter incremented by this task.
        counter: ArcAtomicCount,
    },
    /// Return a task error.
    Fail {
        /// Error returned by the task.
        error: &'static str,
    },
    /// Panic while running.
    Panic {
        /// Panic message.
        message: &'static str,
    },
    /// Panic with a non-string payload while running.
    PanicUsize {
        /// Panic payload.
        payload: usize,
    },
    /// Sleep for the supplied duration, then succeed.
    SleepSuccess {
        /// Sleep duration.
        duration: Duration,
    },
}

/// Configurable runnable task for executor tests.
#[derive(Debug, Clone)]
pub struct TestTask {
    /// Behavior executed by this task.
    action: TestTaskAction,
}

impl TestTask {
    /// Creates a task that succeeds.
    ///
    /// # Returns
    ///
    /// A successful test task.
    pub const fn succeed() -> Self {
        Self {
            action: TestTaskAction::Succeed,
        }
    }

    /// Creates a task that increments `counter` and succeeds.
    ///
    /// # Parameters
    ///
    /// * `counter` - Counter incremented by the task.
    ///
    /// # Returns
    ///
    /// A counting successful test task.
    pub fn count_success(counter: ArcAtomicCount) -> Self {
        Self {
            action: TestTaskAction::CountSuccess { counter },
        }
    }

    /// Creates a task that fails with `error`.
    ///
    /// # Parameters
    ///
    /// * `error` - Error returned by the task.
    ///
    /// # Returns
    ///
    /// A failing test task.
    pub const fn fail(error: &'static str) -> Self {
        Self {
            action: TestTaskAction::Fail { error },
        }
    }

    /// Creates a task that panics with `message`.
    ///
    /// # Parameters
    ///
    /// * `message` - Panic message.
    ///
    /// # Returns
    ///
    /// A panicking test task.
    pub const fn panic(message: &'static str) -> Self {
        Self {
            action: TestTaskAction::Panic { message },
        }
    }

    /// Creates a task that panics with a non-string payload.
    ///
    /// # Parameters
    ///
    /// * `payload` - Panic payload.
    ///
    /// # Returns
    ///
    /// A panicking test task.
    pub const fn panic_usize(payload: usize) -> Self {
        Self {
            action: TestTaskAction::PanicUsize { payload },
        }
    }

    /// Creates a task that sleeps and then succeeds.
    ///
    /// # Parameters
    ///
    /// * `duration` - Sleep duration.
    ///
    /// # Returns
    ///
    /// A sleeping successful test task.
    pub const fn sleep_success(duration: Duration) -> Self {
        Self {
            action: TestTaskAction::SleepSuccess { duration },
        }
    }
}

impl Runnable<&'static str> for TestTask {
    /// Runs this configured test task.
    ///
    /// # Returns
    ///
    /// `Ok(())` for successful actions, or `Err(&'static str)` for
    /// [`TestTaskAction::Fail`].
    ///
    /// # Panics
    ///
    /// Panics when configured with [`TestTaskAction::Panic`].
    fn run(&mut self) -> Result<(), &'static str> {
        match &self.action {
            TestTaskAction::Succeed => Ok(()),
            TestTaskAction::CountSuccess { counter } => {
                counter.inc();
                Ok(())
            }
            TestTaskAction::Fail { error } => Err(*error),
            TestTaskAction::Panic { message } => panic_any(*message),
            TestTaskAction::PanicUsize { payload } => panic_any(*payload),
            TestTaskAction::SleepSuccess { duration } => {
                thread::sleep(*duration);
                Ok(())
            }
        }
    }
}
