use std::time::Duration;

use tokio::time::sleep;
use tracing::warn;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WorkerTickResult {
    Progress,
    Idle,
    Busy,
}

#[derive(Debug, Clone, Copy)]
pub struct WorkerLoopConfig {
    pub poll_interval: Duration,
    pub busy_interval: Duration,
    pub error_interval: Duration,
}

impl WorkerLoopConfig {
    pub fn normalized(self) -> Self {
        Self {
            poll_interval: self.poll_interval.max(Duration::from_millis(250)),
            busy_interval: self.busy_interval.max(Duration::from_millis(250)),
            error_interval: self.error_interval.max(Duration::from_millis(250)),
        }
    }
}

impl Default for WorkerLoopConfig {
    fn default() -> Self {
        Self {
            poll_interval: Duration::from_secs(5),
            busy_interval: Duration::from_secs(1),
            error_interval: Duration::from_secs(5),
        }
    }
}

pub async fn run_worker_loop<F, Fut, E>(
    worker_name: &'static str,
    config: WorkerLoopConfig,
    mut tick: F,
) -> !
where
    F: FnMut() -> Fut,
    Fut: std::future::Future<Output = Result<WorkerTickResult, E>>,
    E: std::fmt::Display,
{
    let config = config.normalized();
    loop {
        let sleep_for = match tick().await {
            Ok(WorkerTickResult::Progress) => Duration::from_millis(250),
            Ok(WorkerTickResult::Busy) => config.busy_interval,
            Ok(WorkerTickResult::Idle) => config.poll_interval,
            Err(err) => {
                warn!(worker = worker_name, error = %err, "worker_tick_failed");
                config.error_interval
            }
        };

        sleep(sleep_for).await;
    }
}

#[cfg(test)]
mod tests {
    use super::{WorkerLoopConfig, WorkerTickResult};
    use std::time::Duration;

    #[test]
    fn worker_loop_config_normalizes_small_values() {
        let cfg = WorkerLoopConfig {
            poll_interval: Duration::from_millis(0),
            busy_interval: Duration::from_millis(10),
            error_interval: Duration::from_millis(200),
        }
        .normalized();

        assert_eq!(cfg.poll_interval, Duration::from_millis(250));
        assert_eq!(cfg.busy_interval, Duration::from_millis(250));
        assert_eq!(cfg.error_interval, Duration::from_millis(250));
    }

    #[test]
    fn worker_tick_result_is_copy() {
        let result = WorkerTickResult::Progress;
        let copied = result;
        assert_eq!(copied, WorkerTickResult::Progress);
    }
}
