use std::time::Duration;

use serde::Deserialize;

use crate::{Error, Result};

/// Bridge task monitor configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TaskMonitorOptions {
    timeout: Option<Duration>,
}

/// Final state reported by a Ghidra task monitor.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskMonitorReport {
    pub timeout_seconds: Option<u64>,
    pub cancelled: bool,
    pub message: String,
    pub progress: i64,
    pub maximum: i64,
}

impl TaskMonitorOptions {
    /// Creates monitor options without a timeout.
    pub const fn none() -> Self {
        Self { timeout: None }
    }

    /// Creates monitor options with a positive timeout.
    pub fn timeout(timeout: Duration) -> Result<Self> {
        validate_timeout(timeout)?;
        Ok(Self {
            timeout: Some(timeout),
        })
    }

    /// Returns the configured timeout duration.
    pub fn timeout_duration(&self) -> Option<Duration> {
        self.timeout
    }

    pub(crate) fn timeout_seconds(&self) -> Result<Option<u64>> {
        self.timeout.map(duration_seconds_ceiling).transpose()
    }
}

impl Default for TaskMonitorOptions {
    fn default() -> Self {
        Self::none()
    }
}

fn validate_timeout(timeout: Duration) -> Result<()> {
    if timeout.is_zero() {
        return Err(Error::invalid_input(
            "task monitor timeout must be greater than zero",
        ));
    }
    let seconds = duration_seconds_ceiling(timeout)?;
    if seconds > i32::MAX as u64 {
        return Err(Error::invalid_input(
            "task monitor timeout must fit in a Java int",
        ));
    }
    Ok(())
}

fn duration_seconds_ceiling(timeout: Duration) -> Result<u64> {
    let seconds = timeout.as_secs();
    if timeout.subsec_nanos() == 0 {
        return Ok(seconds);
    }
    seconds
        .checked_add(1)
        .ok_or_else(|| Error::invalid_input("task monitor timeout is too large"))
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use super::TaskMonitorOptions;

    #[test]
    fn monitor_options_default_to_no_timeout() {
        let options = TaskMonitorOptions::default();
        assert_eq!(options.timeout_duration(), None);
        assert_eq!(options.timeout_seconds().expect("valid timeout"), None);
    }

    #[test]
    fn monitor_options_round_subsecond_timeouts_up() {
        let options = TaskMonitorOptions::timeout(Duration::from_millis(1)).expect("valid timeout");
        assert_eq!(options.timeout_seconds().expect("valid timeout"), Some(1));
    }

    #[test]
    fn monitor_options_reject_zero_timeout() {
        assert!(TaskMonitorOptions::timeout(Duration::ZERO).is_err());
    }

    #[test]
    fn monitor_options_reject_timeouts_larger_than_java_int_seconds() {
        let timeout = Duration::from_secs(i32::MAX as u64 + 1);
        assert!(TaskMonitorOptions::timeout(timeout).is_err());
    }
}
