use std::fmt;
use std::str::FromStr;

#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum TagSortMethod {
    #[default]
    Semver,
    Alphabetical,
    Date,
}

impl TagSortMethod {
    pub const ALL: &[Self] = &[Self::Semver, Self::Alphabetical, Self::Date];
}

impl fmt::Display for TagSortMethod {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Semver => write!(f, "semver"),
            Self::Alphabetical => write!(f, "alphabetical"),
            Self::Date => write!(f, "date"),
        }
    }
}

impl FromStr for TagSortMethod {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "semver" => Ok(Self::Semver),
            "alphabetical" => Ok(Self::Alphabetical),
            "date" => Ok(Self::Date),
            _ => Ok(Self::default()),
        }
    }
}

#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum TagSortDirection {
    Asc,
    #[default]
    Desc,
}

impl TagSortDirection {
    pub const ALL: &[Self] = &[Self::Desc, Self::Asc];
}

impl fmt::Display for TagSortDirection {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Asc => write!(f, "asc"),
            Self::Desc => write!(f, "desc"),
        }
    }
}

impl FromStr for TagSortDirection {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "asc" => Ok(Self::Asc),
            "desc" => Ok(Self::Desc),
            _ => Ok(Self::default()),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct TagSortConfig {
    pub method: TagSortMethod,
    pub direction: TagSortDirection,
}

fn parse_version_segments(s: &str) -> Vec<(Option<u64>, String)> {
    let stripped = s.strip_prefix('v').unwrap_or(s);

    stripped
        .split(&['.', '-'][..])
        .map(|seg| (seg.parse::<u64>().ok(), seg.to_string()))
        .collect()
}

fn cmp_version_strings(a: &str, b: &str) -> std::cmp::Ordering {
    let sa = parse_version_segments(a);
    let sb = parse_version_segments(b);

    for (seg_a, seg_b) in sa.iter().zip(sb.iter()) {
        let ord = match (seg_a.0, seg_b.0) {
            (Some(na), Some(nb)) => na.cmp(&nb),
            (Some(_), None) => std::cmp::Ordering::Less,
            (None, Some(_)) => std::cmp::Ordering::Greater,
            (None, None) => seg_a.1.cmp(&seg_b.1),
        };

        if ord != std::cmp::Ordering::Equal {
            return ord;
        }
    }

    sa.len().cmp(&sb.len())
}

pub fn sort_tags(repo: &git2::Repository, tags: &mut [String], config: &TagSortConfig) {
    match config.method {
        TagSortMethod::Semver => {
            tags.sort_by(|a, b| cmp_version_strings(a, b));
        }

        TagSortMethod::Date => {
            let times: std::collections::HashMap<String, i64> = tags
                .iter()
                .map(|name| {
                    let time = repo
                        .find_reference(&format!("refs/tags/{name}"))
                        .ok()
                        .and_then(|r| r.peel_to_commit().ok())
                        .map(|c| c.time().seconds())
                        .unwrap_or(0);
                    (name.clone(), time)
                })
                .collect();

            tags.sort_by(|a, b| {
                let ta = times.get(a).copied().unwrap_or(0);
                let tb = times.get(b).copied().unwrap_or(0);
                ta.cmp(&tb)
            });
        }

        TagSortMethod::Alphabetical => {
            tags.sort();
        }
    }

    if config.direction == TagSortDirection::Desc {
        tags.reverse();
    }
}
