use chrono::{DateTime, Utc};

use crate::types::{Classification, TakenInfo};

const DEAD_THRESHOLD_DAYS: i64 = 730;
const PLACEHOLDER_DOWNLOAD_LIMIT: u64 = 100;
const ACTIVE_RECENT_DOWNLOADS: u64 = 100;

pub fn classify(info: &TakenInfo) -> Classification {
    if is_placeholder(info) {
        return Classification::Placeholder;
    }
    if info.recent_downloads.unwrap_or(0) >= ACTIVE_RECENT_DOWNLOADS {
        return Classification::Active;
    }
    if is_dead(info) {
        return Classification::Dead;
    }
    Classification::Unknown
}

fn is_placeholder(info: &TakenInfo) -> bool {
    let Some(version) = &info.version else {
        return false;
    };
    let starts_with_zero_zero = version.starts_with("0.0.") || version == "0.0.0";
    let low_downloads = info.downloads.unwrap_or(u64::MAX) < PLACEHOLDER_DOWNLOAD_LIMIT;
    starts_with_zero_zero && low_downloads
}

fn is_dead(info: &TakenInfo) -> bool {
    let recent = info.recent_downloads.unwrap_or(0);
    if recent > 10 {
        return false;
    }
    let Some(last) = info.last_release.as_deref() else {
        return false;
    };
    let Ok(parsed) = DateTime::parse_from_rfc3339(last) else {
        return false;
    };
    let age_days = Utc::now()
        .signed_duration_since(parsed.with_timezone(&Utc))
        .num_days();
    age_days > DEAD_THRESHOLD_DAYS
}

#[cfg(test)]
mod tests {
    use super::*;

    fn taken(version: &str, downloads: u64, recent: u64, last_release: Option<&str>) -> TakenInfo {
        TakenInfo {
            version: Some(version.into()),
            downloads: Some(downloads),
            recent_downloads: Some(recent),
            last_release: last_release.map(String::from),
            ..Default::default()
        }
    }

    #[test]
    fn classifies_placeholder_from_zero_version_and_low_downloads() {
        let info = taken("0.0.1", 5, 0, None);
        assert_eq!(classify(&info), Classification::Placeholder);
    }

    #[test]
    fn active_when_recent_downloads_above_threshold() {
        let info = taken("1.2.3", 5_000, 500, None);
        assert_eq!(classify(&info), Classification::Active);
    }

    #[test]
    fn dead_when_old_and_no_recent_activity() {
        let info = taken("1.0.0", 2_000, 0, Some("2019-01-01T00:00:00Z"));
        assert_eq!(classify(&info), Classification::Dead);
    }

    #[test]
    fn unknown_when_metadata_is_thin() {
        let info = taken("1.0.0", 50, 5, None);
        assert_eq!(classify(&info), Classification::Unknown);
    }
}
