use {
    crate::Version,
    chrono::{offset::Utc, DateTime},
    serde::{Deserialize, Serialize},
    std::{cmp::Ordering, error::Error, fmt, str::FromStr},
};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
/// Represents a Git revision
pub struct GitRev {
    /// the short revision hash
    pub hash: String,
    /// the time of the revision commit
    pub datetime: DateTime<Utc>,
}

impl fmt::Display for GitRev {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "git_{}.{}", self.hash, self.datetime.format("%Y%m%d"))
    }
}

impl PartialOrd for GitRev {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.datetime.partial_cmp(&other.datetime)
    }
}

impl Ord for GitRev {
    fn cmp(&self, other: &Self) -> Ordering {
        self.datetime.cmp(&other.datetime)
    }
}

#[derive(Debug)]
pub struct ParseGitRevError;

impl fmt::Display for ParseGitRevError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error parsing git version")
    }
}

impl Error for ParseGitRevError {}

impl From<chrono::ParseError> for ParseGitRevError {
    fn from(_value: chrono::ParseError) -> Self {
        Self
    }
}

impl FromStr for GitRev {
    type Err = ParseGitRevError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some(gitrev) = s.strip_prefix("git_") {
            if let Some((hash, date)) = gitrev.split_once('_') {
                if hash.len() == 7 {
                    let datetime = DateTime::parse_from_str(date, "%Y%m%d")?;
                    return Ok(Self {
                        hash: hash.to_string(),
                        datetime: datetime.into(),
                    });
                }
            }
        }
        Err(ParseGitRevError)
    }
}

impl TryFrom<Version> for GitRev {
    type Error = ParseGitRevError;

    fn try_from(value: Version) -> Result<Self, Self::Error> {
        match value {
            Version::Git(g) => Ok(g),
            _ => Err(ParseGitRevError),
        }
    }
}

#[cfg(test)]
mod test {
    use std::{thread, time::Duration};

    use super::*;

    #[test]
    fn ord() {
        let a = GitRev {
            hash: "aaab".to_string(),
            datetime: Utc::now(),
        };
        thread::sleep(Duration::from_millis(10));
        let b = GitRev {
            hash: "aaaa".to_string(),
            datetime: Utc::now(),
        };
        assert!(a < b);
    }
}
