use compact_str::CompactString;
use serde::{Deserialize, Serialize};
use std::fmt;
use strum::{Display, EnumIter, EnumString, FromRepr};

#[derive(Debug)]
pub enum JobError {
    NotFound(u32),
    InvalidTransition { from: JobState, to: JobState },
    AlreadyInState(JobState),
}

impl std::error::Error for JobError {}

impl fmt::Display for JobError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            JobError::NotFound(id) => write!(f, "Job {} not found", id),
            JobError::InvalidTransition { from, to } => {
                write!(f, "Invalid transition from {} to {}", from, to)
            }
            JobError::AlreadyInState(state) => write!(f, "Job already in state {}", state),
        }
    }
}

#[derive(
    Debug,
    Deserialize,
    Serialize,
    PartialEq,
    Eq,
    Clone,
    Copy,
    Display,
    EnumIter,
    FromRepr,
    EnumString,
    Hash,
    Ord,
    PartialOrd,
)]
pub enum JobState {
    #[strum(to_string = "Queued", serialize = "PD", serialize = "pd")]
    Queued,
    #[strum(to_string = "Hold", serialize = "H", serialize = "h")]
    Hold,
    #[strum(to_string = "Running", serialize = "R", serialize = "r")]
    Running,
    #[strum(to_string = "Finished", serialize = "CD", serialize = "cd")]
    Finished,
    #[strum(to_string = "Failed", serialize = "F", serialize = "f")]
    Failed,
    #[strum(to_string = "Cancelled", serialize = "CA", serialize = "ca")]
    Cancelled,
    #[strum(to_string = "Timeout", serialize = "TO", serialize = "to")]
    Timeout,
}

#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
pub enum DependencyMode {
    All,
    Any,
}

#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Default)]
pub enum GpuSharingMode {
    #[default]
    Exclusive,
    Shared,
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub enum JobStateReason {
    JobHeldUser,
    WaitingForDependency,
    WaitingForResources,
    CancelledByUser,
    DependencyFailed(u32),
    SystemError(CompactString),
}

impl fmt::Display for JobStateReason {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            JobStateReason::JobHeldUser => write!(f, "JobHeldUser"),
            JobStateReason::WaitingForDependency => write!(f, "Dependency"),
            JobStateReason::WaitingForResources => write!(f, "Resources"),
            JobStateReason::CancelledByUser => write!(f, "CancelledByUser"),
            JobStateReason::DependencyFailed(job_id) => {
                write!(f, "DependencyFailed:{}", job_id)
            }
            JobStateReason::SystemError(msg) => write!(f, "SystemError:{}", msg),
        }
    }
}

impl JobState {
    pub fn short_form(&self) -> &'static str {
        match self {
            JobState::Queued => "PD",
            JobState::Hold => "H",
            JobState::Running => "R",
            JobState::Finished => "CD",
            JobState::Failed => "F",
            JobState::Cancelled => "CA",
            JobState::Timeout => "TO",
        }
    }

    pub fn can_transition_to(self, next: JobState) -> bool {
        use JobState::*;
        matches!(
            (self, next),
            (Queued, Running)
                | (Queued, Hold)
                | (Hold, Queued)
                | (Hold, Cancelled)
                | (Running, Finished)
                | (Running, Failed)
                | (Queued, Cancelled)
                | (Running, Cancelled)
                | (Running, Timeout)
        )
    }

    pub fn is_final(&self) -> bool {
        Self::COMPLETED.contains(self)
    }

    pub const ACTIVE: &'static [JobState] = &[JobState::Queued, JobState::Hold, JobState::Running];

    pub const COMPLETED: &'static [JobState] = &[
        JobState::Finished,
        JobState::Failed,
        JobState::Cancelled,
        JobState::Timeout,
    ];

    pub fn active_states() -> &'static [JobState] {
        Self::ACTIVE
    }

    pub fn completed_states() -> &'static [JobState] {
        Self::COMPLETED
    }
}
