use std::{fmt, str::FromStr};

use anyhow::{Result, bail};

use crate::conventional_commits::ConventionalCommit;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PreRelease {
    Alpha(u64),
    Beta(u64),
    Rc(u64),
}

impl PreRelease {
    fn order_key(&self) -> (u8, u64) {
        match self {
            Self::Alpha(n) => (0, *n),
            Self::Beta(n) => (1, *n),
            Self::Rc(n) => (2, *n),
        }
    }

    fn bump(&self) -> Self {
        match self {
            Self::Alpha(n) => Self::Alpha(n + 1),
            Self::Beta(n) => Self::Beta(n + 1),
            Self::Rc(n) => Self::Rc(n + 1),
        }
    }
}

impl PartialOrd for PreRelease {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for PreRelease {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.order_key().cmp(&other.order_key())
    }
}

impl fmt::Display for PreRelease {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Alpha(n) => write!(f, "a{n}"),
            Self::Beta(n) => write!(f, "b{n}"),
            Self::Rc(n) => write!(f, "rc{n}"),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Suffix {
    Pre(PreRelease),
    Post(u64),
    Dev(u64),
}

impl PartialOrd for Suffix {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Suffix {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.order_key().cmp(&other.order_key())
    }
}

impl Suffix {
    fn order_key(&self) -> (u8, u8, u64) {
        match self {
            Self::Dev(n) => (0, 0, *n),
            Self::Pre(pre) => {
                let (kind, n) = pre.order_key();
                (1, kind, n)
            }
            Self::Post(n) => (3, 0, *n),
        }
    }
}

impl fmt::Display for Suffix {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Pre(pre) => write!(f, "{pre}"),
            Self::Post(n) => write!(f, ".post{n}"),
            Self::Dev(n) => write!(f, ".dev{n}"),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Version {
    pub major: u64,
    pub minor: u64,
    pub patch: u64,
    pub suffix: Option<Suffix>,
}

impl PartialOrd for Version {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Version {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        let base = (self.major, self.minor, self.patch).cmp(&(
            other.major,
            other.minor,
            other.patch,
        ));
        if base != std::cmp::Ordering::Equal {
            return base;
        }
        match (&self.suffix, &other.suffix) {
            (None, None) => std::cmp::Ordering::Equal,
            (None, Some(Suffix::Post(_))) => std::cmp::Ordering::Less,
            (Some(Suffix::Post(_)), None) => std::cmp::Ordering::Greater,
            (None, Some(_)) => std::cmp::Ordering::Greater,
            (Some(_), None) => std::cmp::Ordering::Less,
            (Some(a), Some(b)) => a.cmp(b),
        }
    }
}

impl Version {
    pub fn base(&self) -> Self {
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch,
            suffix: None,
        }
    }

    pub fn bump_major(&self) -> Self {
        Self {
            major: self.major + 1,
            minor: 0,
            patch: 0,
            suffix: None,
        }
    }

    pub fn bump_minor(&self) -> Self {
        Self {
            major: self.major,
            minor: self.minor + 1,
            patch: 0,
            suffix: None,
        }
    }

    pub fn bump_patch(&self) -> Self {
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch + 1,
            suffix: None,
        }
    }

    pub fn bump_pre(&self, kind: &str) -> Result<Self> {
        match &self.suffix {
            Some(Suffix::Pre(pre)) => {
                let same_kind = matches!(
                    (kind, pre),
                    ("a", PreRelease::Alpha(_))
                        | ("b", PreRelease::Beta(_))
                        | ("rc", PreRelease::Rc(_))
                );
                if same_kind {
                    return Ok(Self {
                        major: self.major,
                        minor: self.minor,
                        patch: self.patch,
                        suffix: Some(Suffix::Pre(pre.bump())),
                    });
                }
                let new_pre = match kind {
                    "a" => PreRelease::Alpha(1),
                    "b" => PreRelease::Beta(1),
                    "rc" => PreRelease::Rc(1),
                    _ => bail!("unknown pre-release kind: {kind}"),
                };
                if new_pre < *pre {
                    bail!(
                        "cannot go from {} to {kind} pre-release",
                        pre
                    );
                }
                Ok(Self {
                    major: self.major,
                    minor: self.minor,
                    patch: self.patch,
                    suffix: Some(Suffix::Pre(new_pre)),
                })
            }
            _ => {
                let new_pre = match kind {
                    "a" => PreRelease::Alpha(1),
                    "b" => PreRelease::Beta(1),
                    "rc" => PreRelease::Rc(1),
                    _ => bail!("unknown pre-release kind: {kind}"),
                };
                Ok(Self {
                    major: self.major,
                    minor: self.minor,
                    patch: self.patch,
                    suffix: Some(Suffix::Pre(new_pre)),
                })
            }
        }
    }

    pub fn bump_post(&self) -> Self {
        let n = match &self.suffix {
            Some(Suffix::Post(n)) => n + 1,
            _ => 1,
        };
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch,
            suffix: Some(Suffix::Post(n)),
        }
    }

    pub fn bump_dev(&self) -> Self {
        let n = match &self.suffix {
            Some(Suffix::Dev(n)) => n + 1,
            _ => 1,
        };
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch,
            suffix: Some(Suffix::Dev(n)),
        }
    }

    pub fn finalize(&self) -> Self {
        self.base()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum BumpLevel {
    None,
    Patch,
    Minor,
    Major,
}

impl BumpLevel {
    pub fn from_commits(commits: &[ConventionalCommit]) -> Self {
        commits.iter().fold(Self::None, |level, commit| {
            level.max(Self::from_commit(commit))
        })
    }

    pub fn from_commit(commit: &ConventionalCommit) -> Self {
        if commit.breaking {
            return Self::Major;
        }

        match commit.commit_type.as_str() {
            "feat" => Self::Minor,
            "fix" => Self::Patch,
            _ => Self::None,
        }
    }

    pub fn apply(self, version: &Version) -> Option<Version> {
        match self {
            Self::None => None,
            Self::Patch => Some(version.bump_patch()),
            Self::Minor => Some(version.bump_minor()),
            Self::Major => Some(version.bump_major()),
        }
    }

    pub fn as_str(self) -> &'static str {
        match self {
            Self::None => "none",
            Self::Patch => "patch",
            Self::Minor => "minor",
            Self::Major => "major",
        }
    }
}

impl fmt::Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
        if let Some(suffix) = &self.suffix {
            write!(f, "{suffix}")?;
        }
        Ok(())
    }
}

impl FromStr for Version {
    type Err = anyhow::Error;

    fn from_str(value: &str) -> Result<Self> {
        let (base, suffix) = parse_suffix(value);
        let parts: Vec<_> = base.split('.').collect();
        if parts.len() != 3 {
            bail!("version must contain major.minor.patch");
        }

        Ok(Self {
            major: parts[0].parse()?,
            minor: parts[1].parse()?,
            patch: parts[2].parse()?,
            suffix,
        })
    }
}

fn parse_suffix(value: &str) -> (&str, Option<Suffix>) {
    if let Some((base, n)) = value.rsplit_once(".post")
        && let Ok(n) = n.parse::<u64>()
    {
        return (base, Some(Suffix::Post(n)));
    }
    if let Some((base, n)) = value.rsplit_once(".dev")
        && let Ok(n) = n.parse::<u64>()
    {
        return (base, Some(Suffix::Dev(n)));
    }
    for prefix in ["rc", "a", "b"] {
        if let Some(pos) = value.rfind(prefix) {
            let base = &value[..pos];
            let n_str = &value[pos + prefix.len()..];
            if let Ok(n) = n_str.parse::<u64>() {
                if base.ends_with('.') || base.is_empty() {
                    continue;
                }
                let pre = match prefix {
                    "a" => PreRelease::Alpha(n),
                    "b" => PreRelease::Beta(n),
                    "rc" => PreRelease::Rc(n),
                    _ => unreachable!(),
                };
                return (base, Some(Suffix::Pre(pre)));
            }
        }
    }
    (value, None)
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use crate::conventional_commits::ConventionalCommit;

    use super::{BumpLevel, PreRelease, Suffix, Version};

    #[test]
    fn bumps_minor() {
        let version = Version::from_str("1.2.3").expect("valid version");
        assert_eq!(version.bump_minor().to_string(), "1.3.0");
    }

    #[test]
    fn selects_major_bump_from_breaking_commits() {
        let commits = vec![
            ConventionalCommit::parse_message("fix: patch").expect("parse"),
            ConventionalCommit::parse_message("feat!: break api").expect("parse"),
        ];

        assert_eq!(BumpLevel::from_commits(&commits), BumpLevel::Major);
    }

    #[test]
    fn parses_plain_version() {
        let v = Version::from_str("1.2.3").expect("parse");
        assert_eq!(v.major, 1);
        assert_eq!(v.minor, 2);
        assert_eq!(v.patch, 3);
        assert_eq!(v.suffix, None);
    }

    #[test]
    fn parses_alpha_version() {
        let v = Version::from_str("1.2.3a1").expect("parse");
        assert_eq!(v.suffix, Some(Suffix::Pre(PreRelease::Alpha(1))));
        assert_eq!(v.to_string(), "1.2.3a1");
    }

    #[test]
    fn parses_beta_version() {
        let v = Version::from_str("1.2.3b2").expect("parse");
        assert_eq!(v.suffix, Some(Suffix::Pre(PreRelease::Beta(2))));
        assert_eq!(v.to_string(), "1.2.3b2");
    }

    #[test]
    fn parses_rc_version() {
        let v = Version::from_str("1.2.3rc1").expect("parse");
        assert_eq!(v.suffix, Some(Suffix::Pre(PreRelease::Rc(1))));
        assert_eq!(v.to_string(), "1.2.3rc1");
    }

    #[test]
    fn parses_post_version() {
        let v = Version::from_str("1.2.3.post1").expect("parse");
        assert_eq!(v.suffix, Some(Suffix::Post(1)));
        assert_eq!(v.to_string(), "1.2.3.post1");
    }

    #[test]
    fn parses_dev_version() {
        let v = Version::from_str("1.2.3.dev1").expect("parse");
        assert_eq!(v.suffix, Some(Suffix::Dev(1)));
        assert_eq!(v.to_string(), "1.2.3.dev1");
    }

    #[test]
    fn bump_pre_alpha_from_release() {
        let v = Version::from_str("1.1.0").expect("parse");
        assert_eq!(v.bump_pre("a").unwrap().to_string(), "1.1.0a1");
    }

    #[test]
    fn bump_pre_alpha_increments() {
        let v = Version::from_str("1.1.0a1").expect("parse");
        assert_eq!(v.bump_pre("a").unwrap().to_string(), "1.1.0a2");
    }

    #[test]
    fn bump_pre_alpha_to_rc() {
        let v = Version::from_str("1.1.0a2").expect("parse");
        assert_eq!(v.bump_pre("rc").unwrap().to_string(), "1.1.0rc1");
    }

    #[test]
    fn finalize_pre_release() {
        let v = Version::from_str("1.1.0rc1").expect("parse");
        assert_eq!(v.finalize().to_string(), "1.1.0");
    }

    #[test]
    fn bump_post_from_release() {
        let v = Version::from_str("1.2.3").expect("parse");
        assert_eq!(v.bump_post().to_string(), "1.2.3.post1");
    }

    #[test]
    fn bump_post_increments() {
        let v = Version::from_str("1.2.3.post1").expect("parse");
        assert_eq!(v.bump_post().to_string(), "1.2.3.post2");
    }

    #[test]
    fn bump_dev_from_release() {
        let v = Version::from_str("1.2.3").expect("parse");
        assert_eq!(v.bump_dev().to_string(), "1.2.3.dev1");
    }

    #[test]
    fn bump_dev_increments() {
        let v = Version::from_str("1.2.3.dev1").expect("parse");
        assert_eq!(v.bump_dev().to_string(), "1.2.3.dev2");
    }

    #[test]
    fn ordering_pre_release_before_release() {
        let alpha = Version::from_str("1.0.0a1").expect("parse");
        let release = Version::from_str("1.0.0").expect("parse");
        assert!(alpha < release);
    }

    #[test]
    fn ordering_post_release_after_release() {
        let release = Version::from_str("1.0.0").expect("parse");
        let post = Version::from_str("1.0.0.post1").expect("parse");
        assert!(post > release);
    }

    #[test]
    fn ordering_dev_before_alpha() {
        let dev = Version::from_str("1.0.0.dev1").expect("parse");
        let alpha = Version::from_str("1.0.0a1").expect("parse");
        assert!(dev < alpha);
    }

    #[test]
    fn ordering_alpha_beta_rc() {
        let a = Version::from_str("1.0.0a1").expect("parse");
        let b = Version::from_str("1.0.0b1").expect("parse");
        let rc = Version::from_str("1.0.0rc1").expect("parse");
        assert!(a < b);
        assert!(b < rc);
    }

    #[test]
    fn bump_pre_rejects_downgrade() {
        let v = Version::from_str("1.0.0rc1").expect("parse");
        assert!(v.bump_pre("a").is_err());
    }

    #[test]
    fn bump_major_clears_suffix() {
        let v = Version::from_str("1.0.0a1").expect("parse");
        assert_eq!(v.bump_major().to_string(), "2.0.0");
    }
}
