/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */

pub fn windows_to_iana(windows_name: &str) -> Option<&'static str> {
    let key = windows_name.trim();
    for (w, i) in TABLE {
        if w.eq_ignore_ascii_case(key) {
            return Some(i);
        }
    }
    None
}

pub fn resolve_to_iana(value: &str) -> Option<String> {
    use calcard::common::timezone::Tz;
    use std::str::FromStr;

    let key = value.trim();
    if key.is_empty() {
        return None;
    }
    if let Some(rest) = key.strip_prefix("tzone://Microsoft/") {
        return rest
            .eq_ignore_ascii_case("Utc")
            .then(|| "Etc/UTC".to_owned());
    }
    if let Some(iana) = windows_to_iana(key) {
        return Some(iana.to_owned());
    }
    Tz::from_str(key)
        .ok()
        .and_then(|tz| tz.name().map(|name| name.into_owned()))
}

const TABLE: &[(&str, &str)] = &[
    ("UTC", "Etc/UTC"),
    ("Coordinated Universal Time", "Etc/UTC"),
    ("UTC-11", "Etc/GMT+11"),
    ("UTC-09", "Etc/GMT+9"),
    ("UTC-08", "Etc/GMT+8"),
    ("UTC-02", "Etc/GMT+2"),
    ("UTC+12", "Etc/GMT-12"),
    ("UTC+13", "Etc/GMT-13"),
    ("Dateline Standard Time", "Etc/GMT+12"),
    ("Aleutian Standard Time", "America/Adak"),
    ("Hawaiian Standard Time", "Pacific/Honolulu"),
    ("Marquesas Standard Time", "Pacific/Marquesas"),
    ("Alaskan Standard Time", "America/Anchorage"),
    ("Pacific Standard Time (Mexico)", "America/Tijuana"),
    ("Pacific Standard Time", "America/Los_Angeles"),
    ("US Mountain Standard Time", "America/Phoenix"),
    ("Mountain Standard Time (Mexico)", "America/Mazatlan"),
    ("Mountain Standard Time", "America/Denver"),
    ("Yukon Standard Time", "America/Whitehorse"),
    ("Central America Standard Time", "America/Guatemala"),
    ("Central Standard Time", "America/Chicago"),
    ("Easter Island Standard Time", "Pacific/Easter"),
    ("Central Standard Time (Mexico)", "America/Mexico_City"),
    ("Canada Central Standard Time", "America/Regina"),
    ("SA Pacific Standard Time", "America/Bogota"),
    ("Eastern Standard Time (Mexico)", "America/Cancun"),
    ("Eastern Standard Time", "America/New_York"),
    ("Haiti Standard Time", "America/Port-au-Prince"),
    ("Cuba Standard Time", "America/Havana"),
    ("US Eastern Standard Time", "America/Indianapolis"),
    ("Turks And Caicos Standard Time", "America/Grand_Turk"),
    ("Paraguay Standard Time", "America/Asuncion"),
    ("Atlantic Standard Time", "America/Halifax"),
    ("Venezuela Standard Time", "America/Caracas"),
    ("Central Brazilian Standard Time", "America/Cuiaba"),
    ("SA Western Standard Time", "America/La_Paz"),
    ("Pacific SA Standard Time", "America/Santiago"),
    ("Newfoundland Standard Time", "America/St_Johns"),
    ("Tocantins Standard Time", "America/Araguaina"),
    ("E. South America Standard Time", "America/Sao_Paulo"),
    ("SA Eastern Standard Time", "America/Cayenne"),
    ("Argentina Standard Time", "America/Argentina/Buenos_Aires"),
    ("Greenland Standard Time", "America/Godthab"),
    ("Montevideo Standard Time", "America/Montevideo"),
    ("Magallanes Standard Time", "America/Punta_Arenas"),
    ("Saint Pierre Standard Time", "America/Miquelon"),
    ("Bahia Standard Time", "America/Bahia"),
    ("Mid-Atlantic Standard Time", "Atlantic/South_Georgia"),
    ("Azores Standard Time", "Atlantic/Azores"),
    ("Cape Verde Standard Time", "Atlantic/Cape_Verde"),
    ("GMT Standard Time", "Europe/London"),
    ("Greenwich Standard Time", "Atlantic/Reykjavik"),
    ("Sao Tome Standard Time", "Africa/Sao_Tome"),
    ("Morocco Standard Time", "Africa/Casablanca"),
    ("W. Europe Standard Time", "Europe/Berlin"),
    ("Central Europe Standard Time", "Europe/Budapest"),
    ("Romance Standard Time", "Europe/Paris"),
    ("Central European Standard Time", "Europe/Warsaw"),
    ("W. Central Africa Standard Time", "Africa/Lagos"),
    ("Jordan Standard Time", "Asia/Amman"),
    ("GTB Standard Time", "Europe/Bucharest"),
    ("Middle East Standard Time", "Asia/Beirut"),
    ("Egypt Standard Time", "Africa/Cairo"),
    ("E. Europe Standard Time", "Europe/Chisinau"),
    ("Syria Standard Time", "Asia/Damascus"),
    ("West Bank Standard Time", "Asia/Hebron"),
    ("South Africa Standard Time", "Africa/Johannesburg"),
    ("FLE Standard Time", "Europe/Kiev"),
    ("Israel Standard Time", "Asia/Jerusalem"),
    ("South Sudan Standard Time", "Africa/Juba"),
    ("Kaliningrad Standard Time", "Europe/Kaliningrad"),
    ("Sudan Standard Time", "Africa/Khartoum"),
    ("Libya Standard Time", "Africa/Tripoli"),
    ("Namibia Standard Time", "Africa/Windhoek"),
    ("Arabic Standard Time", "Asia/Baghdad"),
    ("Turkey Standard Time", "Europe/Istanbul"),
    ("Arab Standard Time", "Asia/Riyadh"),
    ("Belarus Standard Time", "Europe/Minsk"),
    ("Russian Standard Time", "Europe/Moscow"),
    ("E. Africa Standard Time", "Africa/Nairobi"),
    ("Iran Standard Time", "Asia/Tehran"),
    ("Arabian Standard Time", "Asia/Dubai"),
    ("Astrakhan Standard Time", "Europe/Astrakhan"),
    ("Azerbaijan Standard Time", "Asia/Baku"),
    ("Russia Time Zone 3", "Europe/Samara"),
    ("Mauritius Standard Time", "Indian/Mauritius"),
    ("Saratov Standard Time", "Europe/Saratov"),
    ("Georgian Standard Time", "Asia/Tbilisi"),
    ("Volgograd Standard Time", "Europe/Volgograd"),
    ("Caucasus Standard Time", "Asia/Yerevan"),
    ("Afghanistan Standard Time", "Asia/Kabul"),
    ("West Asia Standard Time", "Asia/Tashkent"),
    ("Ekaterinburg Standard Time", "Asia/Yekaterinburg"),
    ("Pakistan Standard Time", "Asia/Karachi"),
    ("Qyzylorda Standard Time", "Asia/Qyzylorda"),
    ("India Standard Time", "Asia/Kolkata"),
    ("Sri Lanka Standard Time", "Asia/Colombo"),
    ("Nepal Standard Time", "Asia/Katmandu"),
    ("Central Asia Standard Time", "Asia/Almaty"),
    ("Bangladesh Standard Time", "Asia/Dhaka"),
    ("Omsk Standard Time", "Asia/Omsk"),
    ("Myanmar Standard Time", "Asia/Rangoon"),
    ("SE Asia Standard Time", "Asia/Bangkok"),
    ("Altai Standard Time", "Asia/Barnaul"),
    ("W. Mongolia Standard Time", "Asia/Hovd"),
    ("North Asia Standard Time", "Asia/Krasnoyarsk"),
    ("N. Central Asia Standard Time", "Asia/Novosibirsk"),
    ("Tomsk Standard Time", "Asia/Tomsk"),
    ("China Standard Time", "Asia/Shanghai"),
    ("North Asia East Standard Time", "Asia/Irkutsk"),
    ("Singapore Standard Time", "Asia/Singapore"),
    ("W. Australia Standard Time", "Australia/Perth"),
    ("Taipei Standard Time", "Asia/Taipei"),
    ("Ulaanbaatar Standard Time", "Asia/Ulaanbaatar"),
    ("Aus Central W. Standard Time", "Australia/Eucla"),
    ("Transbaikal Standard Time", "Asia/Chita"),
    ("Tokyo Standard Time", "Asia/Tokyo"),
    ("North Korea Standard Time", "Asia/Pyongyang"),
    ("Korea Standard Time", "Asia/Seoul"),
    ("Yakutsk Standard Time", "Asia/Yakutsk"),
    ("Cen. Australia Standard Time", "Australia/Adelaide"),
    ("AUS Central Standard Time", "Australia/Darwin"),
    ("E. Australia Standard Time", "Australia/Brisbane"),
    ("AUS Eastern Standard Time", "Australia/Sydney"),
    ("West Pacific Standard Time", "Pacific/Port_Moresby"),
    ("Tasmania Standard Time", "Australia/Hobart"),
    ("Vladivostok Standard Time", "Asia/Vladivostok"),
    ("Lord Howe Standard Time", "Australia/Lord_Howe"),
    ("Bougainville Standard Time", "Pacific/Bougainville"),
    ("Russia Time Zone 10", "Asia/Srednekolymsk"),
    ("Magadan Standard Time", "Asia/Magadan"),
    ("Norfolk Standard Time", "Pacific/Norfolk"),
    ("Sakhalin Standard Time", "Asia/Sakhalin"),
    ("Central Pacific Standard Time", "Pacific/Guadalcanal"),
    ("Russia Time Zone 11", "Asia/Kamchatka"),
    ("New Zealand Standard Time", "Pacific/Auckland"),
    ("Fiji Standard Time", "Pacific/Fiji"),
    ("Kamchatka Standard Time", "Asia/Kamchatka"),
    ("Chatham Islands Standard Time", "Pacific/Chatham"),
    ("Tonga Standard Time", "Pacific/Tongatapu"),
    ("Samoa Standard Time", "Pacific/Apia"),
    ("Line Islands Standard Time", "Pacific/Kiritimati"),
];

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

    #[test]
    fn pacific_standard_time_maps_to_los_angeles() {
        assert_eq!(
            windows_to_iana("Pacific Standard Time"),
            Some("America/Los_Angeles")
        );
    }

    #[test]
    fn romance_standard_time_maps_to_paris() {
        assert_eq!(
            windows_to_iana("Romance Standard Time"),
            Some("Europe/Paris")
        );
    }

    #[test]
    fn unknown_zone_returns_none() {
        assert_eq!(windows_to_iana("Made Up Zone"), None);
    }

    #[test]
    fn case_insensitive_lookup() {
        assert_eq!(
            windows_to_iana("eastern standard time"),
            Some("America/New_York")
        );
    }

    #[test]
    fn whitespace_is_trimmed() {
        assert_eq!(
            windows_to_iana("  GMT Standard Time  "),
            Some("Europe/London")
        );
    }

    #[test]
    fn full_cldr_table_has_at_least_140_rows() {
        assert!(TABLE.len() >= 140, "got {} rows", TABLE.len());
    }

    #[test]
    fn spot_check_coverage_across_continents() {
        assert_eq!(windows_to_iana("Tokyo Standard Time"), Some("Asia/Tokyo"));
        assert_eq!(
            windows_to_iana("AUS Eastern Standard Time"),
            Some("Australia/Sydney")
        );
        assert_eq!(
            windows_to_iana("Cen. Australia Standard Time"),
            Some("Australia/Adelaide")
        );
        assert_eq!(
            windows_to_iana("Greenland Standard Time"),
            Some("America/Godthab")
        );
        assert_eq!(
            windows_to_iana("China Standard Time"),
            Some("Asia/Shanghai")
        );
        assert_eq!(
            windows_to_iana("Israel Standard Time"),
            Some("Asia/Jerusalem")
        );
        assert_eq!(
            windows_to_iana("Chatham Islands Standard Time"),
            Some("Pacific/Chatham")
        );
        assert_eq!(
            windows_to_iana("Newfoundland Standard Time"),
            Some("America/St_Johns")
        );
        assert_eq!(windows_to_iana("UTC-08"), Some("Etc/GMT+8"));
        assert_eq!(windows_to_iana("UTC+13"), Some("Etc/GMT-13"));
        assert_eq!(
            windows_to_iana("Yukon Standard Time"),
            Some("America/Whitehorse")
        );
        assert_eq!(
            windows_to_iana("Aus Central W. Standard Time"),
            Some("Australia/Eucla")
        );
    }

    #[test]
    fn resolve_handles_windows_iana_offset_and_microsoft_tzone() {
        assert_eq!(
            resolve_to_iana("Romance Standard Time").as_deref(),
            Some("Europe/Paris")
        );
        assert_eq!(
            resolve_to_iana("Europe/Paris").as_deref(),
            Some("Europe/Paris")
        );
        assert_eq!(resolve_to_iana("UTC").as_deref(), Some("Etc/UTC"));
        assert_eq!(
            resolve_to_iana("tzone://Microsoft/Utc").as_deref(),
            Some("Etc/UTC")
        );
        assert_eq!(resolve_to_iana("tzone://Microsoft/Custom"), None);
        assert_eq!(resolve_to_iana("Made Up Zone"), None);
        assert_eq!(resolve_to_iana(""), None);
    }

    #[test]
    fn coordinated_universal_time_alias_resolves_to_utc() {
        assert_eq!(
            windows_to_iana("Coordinated Universal Time"),
            Some("Etc/UTC")
        );
    }
}
