//! Timezone conversions: city name / abbreviation lookup to IANA timezone,
//! time conversion between zones with DST awareness via `chrono-tz`.

use chrono::{NaiveDate, NaiveTime, TimeZone, Timelike};
use chrono_tz::Tz;
use std::collections::HashMap;
use std::sync::OnceLock;

/// The result of converting a time between timezones.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ZonedTimeResult {
    /// The hour in 12-hour format (1-12).
    pub hour12: u32,
    /// The minute.
    pub minute: u32,
    /// Whether this is PM.
    pub is_pm: bool,
    /// The timezone abbreviation in the target zone (e.g. "EDT", "JST").
    pub tz_abbr: String,
    /// The date in the target timezone.
    pub date: NaiveDate,
    /// Whether the date differs from the source date (date boundary crossing).
    pub date_changed: bool,
}

/// Resolve a timezone string to a `chrono_tz::Tz`.
///
/// Accepts:
/// - City names: "Tokyo", "New York", "Los Angeles"
/// - Abbreviations: "EST", "PST", "CET", "JST"
/// - Disambiguation: "Portland, ME" vs "Portland, OR"
/// - Country names: "Japan", "India", "UK"
/// - IANA identifiers: "America/New_York"
pub fn resolve_timezone(name: &str) -> Option<Tz> {
    let normalized = name.trim().to_lowercase();

    // Try abbreviation first (exact match)
    if let Some(tz) = abbreviation_map().get(normalized.as_str()) {
        return Some(*tz);
    }

    // Try city name lookup
    if let Some(tz) = city_map().get(normalized.as_str()) {
        return Some(*tz);
    }

    // Try direct IANA parse
    if let Ok(tz) = normalized.parse::<Tz>() {
        return Some(tz);
    }

    // Try replacing spaces with underscores for IANA
    let iana_attempt = name.trim().replace(' ', "_");
    if let Ok(tz) = iana_attempt.parse::<Tz>() {
        return Some(tz);
    }

    None
}

/// Convert a time from one timezone to another.
///
/// - `hour24`, `minute`: the source time in 24-hour format
/// - `source_date`: the date in the source timezone
/// - `source_tz`, `target_tz`: resolved timezone objects
///
/// Returns `None` if the local time is ambiguous or invalid (e.g. during DST gap).
pub fn convert_time(
    hour24: u32,
    minute: u32,
    source_date: NaiveDate,
    source_tz: Tz,
    target_tz: Tz,
) -> Option<ZonedTimeResult> {
    let source_time = NaiveTime::from_hms_opt(hour24, minute, 0)?;
    let source_naive = source_date.and_time(source_time);

    let source_dt = source_tz
        .from_local_datetime(&source_naive)
        .single()?;

    let target_dt = source_dt.with_timezone(&target_tz);
    let target_date = target_dt.date_naive();
    let target_hour = target_dt.hour();
    let target_minute = target_dt.minute();
    let tz_abbr = target_dt.format("%Z").to_string();

    let (h12, is_pm) = crate::datetime::time_math::to_12h(target_hour);

    Some(ZonedTimeResult {
        hour12: h12,
        minute: target_minute,
        is_pm,
        tz_abbr,
        date: target_date,
        date_changed: target_date != source_date,
    })
}

/// Get the current time in a given timezone.
pub fn current_time_in(
    tz: Tz,
    now_utc: chrono::DateTime<chrono::Utc>,
) -> ZonedTimeResult {
    let in_tz = now_utc.with_timezone(&tz);
    let date = in_tz.date_naive();
    let hour = in_tz.hour();
    let minute = in_tz.minute();
    let tz_abbr = in_tz.format("%Z").to_string();
    let (h12, is_pm) = crate::datetime::time_math::to_12h(hour);

    ZonedTimeResult {
        hour12: h12,
        minute,
        is_pm,
        tz_abbr,
        date,
        date_changed: false,
    }
}

/// Format a `ZonedTimeResult` for display.
pub fn format_zoned_time(
    result: &ZonedTimeResult,
    date_format: crate::datetime::date_math::DateFormat,
) -> String {
    let ampm = if result.is_pm { "PM" } else { "AM" };
    let time_str = format!("{}:{:02} {} {}", result.hour12, result.minute, ampm, result.tz_abbr);
    if result.date_changed {
        let date_str = crate::datetime::date_math::format_date(result.date, date_format);
        format!("{} ({})", time_str, date_str)
    } else {
        time_str
    }
}

/// Number of city aliases in the database.
pub fn city_alias_count() -> usize {
    city_map().len()
}

// ---------------------------------------------------------------------------
// Internal lookup tables
// ---------------------------------------------------------------------------

fn abbreviation_map() -> &'static HashMap<&'static str, Tz> {
    static MAP: OnceLock<HashMap<&'static str, Tz>> = OnceLock::new();
    MAP.get_or_init(|| {
        let mut m = HashMap::new();
        // North America
        m.insert("est", Tz::America__New_York);
        m.insert("edt", Tz::America__New_York);
        m.insert("cst", Tz::America__Chicago);
        m.insert("cdt", Tz::America__Chicago);
        m.insert("mst", Tz::America__Denver);
        m.insert("mdt", Tz::America__Denver);
        m.insert("pst", Tz::America__Los_Angeles);
        m.insert("pdt", Tz::America__Los_Angeles);
        m.insert("akst", Tz::America__Anchorage);
        m.insert("akdt", Tz::America__Anchorage);
        m.insert("hst", Tz::Pacific__Honolulu);
        m.insert("ast", Tz::America__Halifax);
        m.insert("adt", Tz::America__Halifax);
        m.insert("nst", Tz::America__St_Johns);
        m.insert("ndt", Tz::America__St_Johns);
        // Europe
        m.insert("gmt", Tz::Europe__London);
        m.insert("bst", Tz::Europe__London);
        m.insert("utc", Tz::UTC);
        m.insert("wet", Tz::Europe__Lisbon);
        m.insert("west", Tz::Europe__Lisbon);
        m.insert("cet", Tz::Europe__Paris);
        m.insert("cest", Tz::Europe__Paris);
        m.insert("eet", Tz::Europe__Bucharest);
        m.insert("eest", Tz::Europe__Bucharest);
        m.insert("msk", Tz::Europe__Moscow);
        // Asia
        m.insert("ist", Tz::Asia__Kolkata);
        m.insert("jst", Tz::Asia__Tokyo);
        m.insert("kst", Tz::Asia__Seoul);
        m.insert("hkt", Tz::Asia__Hong_Kong);
        m.insert("sgt", Tz::Asia__Singapore);
        m.insert("pht", Tz::Asia__Manila);
        m.insert("wib", Tz::Asia__Jakarta);
        m.insert("wit", Tz::Asia__Jayapura);
        m.insert("wita", Tz::Asia__Makassar);
        m.insert("ict", Tz::Asia__Bangkok);
        m.insert("bdt", Tz::Asia__Dhaka);
        m.insert("pkt", Tz::Asia__Karachi);
        m.insert("aft", Tz::Asia__Kabul);
        m.insert("irst", Tz::Asia__Tehran);
        m.insert("gst", Tz::Asia__Dubai);
        m.insert("trt", Tz::Europe__Istanbul);
        // Oceania
        m.insert("aest", Tz::Australia__Sydney);
        m.insert("aedt", Tz::Australia__Sydney);
        m.insert("acst", Tz::Australia__Adelaide);
        m.insert("acdt", Tz::Australia__Adelaide);
        m.insert("awst", Tz::Australia__Perth);
        m.insert("nzst", Tz::Pacific__Auckland);
        m.insert("nzdt", Tz::Pacific__Auckland);
        // South America
        m.insert("brt", Tz::America__Sao_Paulo);
        m.insert("art", Tz::America__Argentina__Buenos_Aires);
        m.insert("clt", Tz::America__Santiago);
        m.insert("pet", Tz::America__Lima);
        m.insert("cot", Tz::America__Bogota);
        m.insert("vet", Tz::America__Caracas);
        // Africa
        m.insert("cat", Tz::Africa__Maputo);
        m.insert("eat", Tz::Africa__Nairobi);
        m.insert("wat", Tz::Africa__Lagos);
        m.insert("sast", Tz::Africa__Johannesburg);
        m
    })
}

fn city_map() -> &'static HashMap<&'static str, Tz> {
    static MAP: OnceLock<HashMap<&'static str, Tz>> = OnceLock::new();
    MAP.get_or_init(|| {
        let mut m = HashMap::new();

        // ===== NORTH AMERICA =====
        m.insert("new york", Tz::America__New_York);
        m.insert("new york city", Tz::America__New_York);
        m.insert("nyc", Tz::America__New_York);
        m.insert("manhattan", Tz::America__New_York);
        m.insert("brooklyn", Tz::America__New_York);
        m.insert("queens", Tz::America__New_York);
        m.insert("bronx", Tz::America__New_York);
        m.insert("los angeles", Tz::America__Los_Angeles);
        m.insert("la", Tz::America__Los_Angeles);
        m.insert("hollywood", Tz::America__Los_Angeles);
        m.insert("chicago", Tz::America__Chicago);
        m.insert("houston", Tz::America__Chicago);
        m.insert("phoenix", Tz::America__Phoenix);
        m.insert("philadelphia", Tz::America__New_York);
        m.insert("san antonio", Tz::America__Chicago);
        m.insert("san diego", Tz::America__Los_Angeles);
        m.insert("dallas", Tz::America__Chicago);
        m.insert("san jose", Tz::America__Los_Angeles);
        m.insert("austin", Tz::America__Chicago);
        m.insert("jacksonville", Tz::America__New_York);
        m.insert("fort worth", Tz::America__Chicago);
        m.insert("columbus", Tz::America__New_York);
        m.insert("charlotte", Tz::America__New_York);
        m.insert("san francisco", Tz::America__Los_Angeles);
        m.insert("sf", Tz::America__Los_Angeles);
        m.insert("indianapolis", Tz::America__Indiana__Indianapolis);
        m.insert("seattle", Tz::America__Los_Angeles);
        m.insert("denver", Tz::America__Denver);
        m.insert("washington", Tz::America__New_York);
        m.insert("washington dc", Tz::America__New_York);
        m.insert("dc", Tz::America__New_York);
        m.insert("nashville", Tz::America__Chicago);
        m.insert("oklahoma city", Tz::America__Chicago);
        m.insert("el paso", Tz::America__Denver);
        m.insert("boston", Tz::America__New_York);
        m.insert("portland", Tz::America__Los_Angeles);
        m.insert("portland, or", Tz::America__Los_Angeles);
        m.insert("portland, oregon", Tz::America__Los_Angeles);
        m.insert("portland, me", Tz::America__New_York);
        m.insert("portland, maine", Tz::America__New_York);
        m.insert("las vegas", Tz::America__Los_Angeles);
        m.insert("vegas", Tz::America__Los_Angeles);
        m.insert("memphis", Tz::America__Chicago);
        m.insert("louisville", Tz::America__Kentucky__Louisville);
        m.insert("baltimore", Tz::America__New_York);
        m.insert("milwaukee", Tz::America__Chicago);
        m.insert("albuquerque", Tz::America__Denver);
        m.insert("tucson", Tz::America__Phoenix);
        m.insert("fresno", Tz::America__Los_Angeles);
        m.insert("sacramento", Tz::America__Los_Angeles);
        m.insert("mesa", Tz::America__Phoenix);
        m.insert("atlanta", Tz::America__New_York);
        m.insert("kansas city", Tz::America__Chicago);
        m.insert("colorado springs", Tz::America__Denver);
        m.insert("omaha", Tz::America__Chicago);
        m.insert("raleigh", Tz::America__New_York);
        m.insert("miami", Tz::America__New_York);
        m.insert("tampa", Tz::America__New_York);
        m.insert("orlando", Tz::America__New_York);
        m.insert("cleveland", Tz::America__New_York);
        m.insert("pittsburgh", Tz::America__New_York);
        m.insert("cincinnati", Tz::America__New_York);
        m.insert("minneapolis", Tz::America__Chicago);
        m.insert("st louis", Tz::America__Chicago);
        m.insert("saint louis", Tz::America__Chicago);
        m.insert("new orleans", Tz::America__Chicago);
        m.insert("detroit", Tz::America__Detroit);
        m.insert("salt lake city", Tz::America__Denver);
        m.insert("honolulu", Tz::Pacific__Honolulu);
        m.insert("hawaii", Tz::Pacific__Honolulu);
        m.insert("anchorage", Tz::America__Anchorage);
        m.insert("alaska", Tz::America__Anchorage);
        m.insert("boise", Tz::America__Boise);
        m.insert("richmond", Tz::America__New_York);
        m.insert("buffalo", Tz::America__New_York);

        // Canada
        m.insert("toronto", Tz::America__Toronto);
        m.insert("vancouver", Tz::America__Vancouver);
        m.insert("montreal", Tz::America__Montreal);
        m.insert("calgary", Tz::America__Edmonton);
        m.insert("edmonton", Tz::America__Edmonton);
        m.insert("ottawa", Tz::America__Toronto);
        m.insert("winnipeg", Tz::America__Winnipeg);
        m.insert("halifax", Tz::America__Halifax);
        m.insert("regina", Tz::America__Regina);

        // Mexico
        m.insert("mexico city", Tz::America__Mexico_City);
        m.insert("guadalajara", Tz::America__Mexico_City);
        m.insert("monterrey", Tz::America__Monterrey);
        m.insert("cancun", Tz::America__Cancun);
        m.insert("tijuana", Tz::America__Tijuana);

        // ===== SOUTH AMERICA =====
        m.insert("sao paulo", Tz::America__Sao_Paulo);
        m.insert("rio de janeiro", Tz::America__Sao_Paulo);
        m.insert("rio", Tz::America__Sao_Paulo);
        m.insert("buenos aires", Tz::America__Argentina__Buenos_Aires);
        m.insert("santiago", Tz::America__Santiago);
        m.insert("lima", Tz::America__Lima);
        m.insert("bogota", Tz::America__Bogota);
        m.insert("caracas", Tz::America__Caracas);
        m.insert("montevideo", Tz::America__Montevideo);

        // ===== EUROPE =====
        m.insert("london", Tz::Europe__London);
        m.insert("edinburgh", Tz::Europe__London);
        m.insert("manchester", Tz::Europe__London);
        m.insert("glasgow", Tz::Europe__London);
        m.insert("dublin", Tz::Europe__Dublin);
        m.insert("paris", Tz::Europe__Paris);
        m.insert("berlin", Tz::Europe__Berlin);
        m.insert("amsterdam", Tz::Europe__Amsterdam);
        m.insert("brussels", Tz::Europe__Brussels);
        m.insert("zurich", Tz::Europe__Zurich);
        m.insert("geneva", Tz::Europe__Zurich);
        m.insert("vienna", Tz::Europe__Vienna);
        m.insert("munich", Tz::Europe__Berlin);
        m.insert("frankfurt", Tz::Europe__Berlin);
        m.insert("rome", Tz::Europe__Rome);
        m.insert("milan", Tz::Europe__Rome);
        m.insert("madrid", Tz::Europe__Madrid);
        m.insert("barcelona", Tz::Europe__Madrid);
        m.insert("lisbon", Tz::Europe__Lisbon);
        m.insert("athens", Tz::Europe__Athens);
        m.insert("stockholm", Tz::Europe__Stockholm);
        m.insert("oslo", Tz::Europe__Oslo);
        m.insert("copenhagen", Tz::Europe__Copenhagen);
        m.insert("helsinki", Tz::Europe__Helsinki);
        m.insert("moscow", Tz::Europe__Moscow);
        m.insert("st petersburg", Tz::Europe__Moscow);
        m.insert("saint petersburg", Tz::Europe__Moscow);
        m.insert("warsaw", Tz::Europe__Warsaw);
        m.insert("prague", Tz::Europe__Prague);
        m.insert("budapest", Tz::Europe__Budapest);
        m.insert("bucharest", Tz::Europe__Bucharest);
        m.insert("istanbul", Tz::Europe__Istanbul);
        m.insert("kyiv", Tz::Europe__Kyiv);
        m.insert("kiev", Tz::Europe__Kyiv);

        // ===== ASIA =====
        m.insert("tokyo", Tz::Asia__Tokyo);
        m.insert("osaka", Tz::Asia__Tokyo);
        m.insert("kyoto", Tz::Asia__Tokyo);
        m.insert("seoul", Tz::Asia__Seoul);
        m.insert("busan", Tz::Asia__Seoul);
        m.insert("beijing", Tz::Asia__Shanghai);
        m.insert("shanghai", Tz::Asia__Shanghai);
        m.insert("guangzhou", Tz::Asia__Shanghai);
        m.insert("shenzhen", Tz::Asia__Shanghai);
        m.insert("hong kong", Tz::Asia__Hong_Kong);
        m.insert("taipei", Tz::Asia__Taipei);
        m.insert("singapore", Tz::Asia__Singapore);
        m.insert("bangkok", Tz::Asia__Bangkok);
        m.insert("jakarta", Tz::Asia__Jakarta);
        m.insert("bali", Tz::Asia__Makassar);
        m.insert("kuala lumpur", Tz::Asia__Kuala_Lumpur);
        m.insert("manila", Tz::Asia__Manila);
        m.insert("ho chi minh city", Tz::Asia__Ho_Chi_Minh);
        m.insert("saigon", Tz::Asia__Ho_Chi_Minh);
        m.insert("hanoi", Tz::Asia__Ho_Chi_Minh);
        m.insert("mumbai", Tz::Asia__Kolkata);
        m.insert("delhi", Tz::Asia__Kolkata);
        m.insert("new delhi", Tz::Asia__Kolkata);
        m.insert("bangalore", Tz::Asia__Kolkata);
        m.insert("bengaluru", Tz::Asia__Kolkata);
        m.insert("chennai", Tz::Asia__Kolkata);
        m.insert("kolkata", Tz::Asia__Kolkata);
        m.insert("hyderabad", Tz::Asia__Kolkata);
        m.insert("karachi", Tz::Asia__Karachi);
        m.insert("lahore", Tz::Asia__Karachi);
        m.insert("islamabad", Tz::Asia__Karachi);
        m.insert("dhaka", Tz::Asia__Dhaka);
        m.insert("colombo", Tz::Asia__Colombo);
        m.insert("kathmandu", Tz::Asia__Kathmandu);
        m.insert("dubai", Tz::Asia__Dubai);
        m.insert("abu dhabi", Tz::Asia__Dubai);
        m.insert("doha", Tz::Asia__Qatar);
        m.insert("riyadh", Tz::Asia__Riyadh);
        m.insert("jeddah", Tz::Asia__Riyadh);
        m.insert("tehran", Tz::Asia__Tehran);
        m.insert("baghdad", Tz::Asia__Baghdad);
        m.insert("beirut", Tz::Asia__Beirut);
        m.insert("jerusalem", Tz::Asia__Jerusalem);
        m.insert("tel aviv", Tz::Asia__Jerusalem);
        m.insert("kabul", Tz::Asia__Kabul);

        // ===== AFRICA =====
        m.insert("cairo", Tz::Africa__Cairo);
        m.insert("lagos", Tz::Africa__Lagos);
        m.insert("nairobi", Tz::Africa__Nairobi);
        m.insert("johannesburg", Tz::Africa__Johannesburg);
        m.insert("cape town", Tz::Africa__Johannesburg);
        m.insert("casablanca", Tz::Africa__Casablanca);
        m.insert("addis ababa", Tz::Africa__Addis_Ababa);
        m.insert("accra", Tz::Africa__Accra);
        m.insert("dakar", Tz::Africa__Dakar);

        // ===== OCEANIA =====
        m.insert("sydney", Tz::Australia__Sydney);
        m.insert("melbourne", Tz::Australia__Melbourne);
        m.insert("brisbane", Tz::Australia__Brisbane);
        m.insert("perth", Tz::Australia__Perth);
        m.insert("adelaide", Tz::Australia__Adelaide);
        m.insert("canberra", Tz::Australia__Sydney);
        m.insert("darwin", Tz::Australia__Darwin);
        m.insert("auckland", Tz::Pacific__Auckland);
        m.insert("wellington", Tz::Pacific__Auckland);

        // ===== COUNTRY ALIASES =====
        m.insert("japan", Tz::Asia__Tokyo);
        m.insert("korea", Tz::Asia__Seoul);
        m.insert("south korea", Tz::Asia__Seoul);
        m.insert("china", Tz::Asia__Shanghai);
        m.insert("india", Tz::Asia__Kolkata);
        m.insert("australia", Tz::Australia__Sydney);
        m.insert("brazil", Tz::America__Sao_Paulo);
        m.insert("germany", Tz::Europe__Berlin);
        m.insert("france", Tz::Europe__Paris);
        m.insert("spain", Tz::Europe__Madrid);
        m.insert("italy", Tz::Europe__Rome);
        m.insert("uk", Tz::Europe__London);
        m.insert("england", Tz::Europe__London);
        m.insert("ireland", Tz::Europe__Dublin);
        m.insert("russia", Tz::Europe__Moscow);
        m.insert("turkey", Tz::Europe__Istanbul);
        m.insert("egypt", Tz::Africa__Cairo);
        m.insert("south africa", Tz::Africa__Johannesburg);
        m.insert("nigeria", Tz::Africa__Lagos);
        m.insert("kenya", Tz::Africa__Nairobi);
        m.insert("thailand", Tz::Asia__Bangkok);
        m.insert("vietnam", Tz::Asia__Ho_Chi_Minh);
        m.insert("philippines", Tz::Asia__Manila);
        m.insert("indonesia", Tz::Asia__Jakarta);
        m.insert("malaysia", Tz::Asia__Kuala_Lumpur);
        m.insert("pakistan", Tz::Asia__Karachi);
        m.insert("bangladesh", Tz::Asia__Dhaka);
        m.insert("sri lanka", Tz::Asia__Colombo);
        m.insert("nepal", Tz::Asia__Kathmandu);
        m.insert("iran", Tz::Asia__Tehran);
        m.insert("iraq", Tz::Asia__Baghdad);
        m.insert("saudi arabia", Tz::Asia__Riyadh);
        m.insert("uae", Tz::Asia__Dubai);
        m.insert("qatar", Tz::Asia__Qatar);
        m.insert("israel", Tz::Asia__Jerusalem);
        m.insert("mexico", Tz::America__Mexico_City);
        m.insert("argentina", Tz::America__Argentina__Buenos_Aires);
        m.insert("colombia", Tz::America__Bogota);
        m.insert("peru", Tz::America__Lima);
        m.insert("chile", Tz::America__Santiago);
        m.insert("new zealand", Tz::Pacific__Auckland);
        m.insert("portugal", Tz::Europe__Lisbon);
        m.insert("netherlands", Tz::Europe__Amsterdam);
        m.insert("holland", Tz::Europe__Amsterdam);
        m.insert("belgium", Tz::Europe__Brussels);
        m.insert("switzerland", Tz::Europe__Zurich);
        m.insert("austria", Tz::Europe__Vienna);
        m.insert("poland", Tz::Europe__Warsaw);
        m.insert("czech republic", Tz::Europe__Prague);
        m.insert("czechia", Tz::Europe__Prague);
        m.insert("hungary", Tz::Europe__Budapest);
        m.insert("romania", Tz::Europe__Bucharest);
        m.insert("greece", Tz::Europe__Athens);
        m.insert("sweden", Tz::Europe__Stockholm);
        m.insert("norway", Tz::Europe__Oslo);
        m.insert("denmark", Tz::Europe__Copenhagen);
        m.insert("finland", Tz::Europe__Helsinki);
        m.insert("ukraine", Tz::Europe__Kyiv);
        m.insert("taiwan", Tz::Asia__Taipei);
        m.insert("morocco", Tz::Africa__Casablanca);

        m
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{TimeZone, Utc};

    #[test]
    fn test_resolve_city_name() {
        assert_eq!(resolve_timezone("Tokyo"), Some(Tz::Asia__Tokyo));
        assert_eq!(resolve_timezone("London"), Some(Tz::Europe__London));
        assert_eq!(resolve_timezone("New York"), Some(Tz::America__New_York));
    }

    #[test]
    fn test_resolve_abbreviation() {
        assert_eq!(resolve_timezone("EST"), Some(Tz::America__New_York));
        assert_eq!(resolve_timezone("PST"), Some(Tz::America__Los_Angeles));
        assert_eq!(resolve_timezone("CET"), Some(Tz::Europe__Paris));
        assert_eq!(resolve_timezone("JST"), Some(Tz::Asia__Tokyo));
    }

    #[test]
    fn test_resolve_case_insensitive() {
        assert_eq!(resolve_timezone("tokyo"), Some(Tz::Asia__Tokyo));
        assert_eq!(resolve_timezone("TOKYO"), Some(Tz::Asia__Tokyo));
    }

    #[test]
    fn test_resolve_disambiguation() {
        assert_eq!(resolve_timezone("Portland"), Some(Tz::America__Los_Angeles));
        assert_eq!(resolve_timezone("Portland, ME"), Some(Tz::America__New_York));
    }

    #[test]
    fn test_resolve_country() {
        assert_eq!(resolve_timezone("Japan"), Some(Tz::Asia__Tokyo));
        assert_eq!(resolve_timezone("India"), Some(Tz::Asia__Kolkata));
        assert_eq!(resolve_timezone("UK"), Some(Tz::Europe__London));
    }

    #[test]
    fn test_resolve_unknown() {
        assert_eq!(resolve_timezone("Narnia"), None);
    }

    #[test]
    fn test_convert_tokyo_to_london_winter() {
        // March 17, 2026: London is still on GMT (DST starts Mar 29).
        // 3:00 PM JST = 06:00 UTC = 06:00 GMT
        let source_date = NaiveDate::from_ymd_opt(2026, 3, 17).unwrap();
        let result = convert_time(
            15,
            0,
            source_date,
            Tz::Asia__Tokyo,
            Tz::Europe__London,
        )
        .unwrap();
        assert_eq!(result.hour12, 6);
        assert_eq!(result.minute, 0);
        assert!(!result.is_pm);
        assert_eq!(result.tz_abbr, "GMT");
        assert!(!result.date_changed);
    }

    #[test]
    fn test_convert_tokyo_to_london_summer() {
        // July 15, 2026: London is on BST (UTC+1).
        // 3:00 PM JST = 06:00 UTC = 07:00 BST
        let source_date = NaiveDate::from_ymd_opt(2026, 7, 15).unwrap();
        let result = convert_time(
            15,
            0,
            source_date,
            Tz::Asia__Tokyo,
            Tz::Europe__London,
        )
        .unwrap();
        assert_eq!(result.hour12, 7);
        assert_eq!(result.minute, 0);
        assert!(!result.is_pm);
        assert_eq!(result.tz_abbr, "BST");
    }

    #[test]
    fn test_convert_date_boundary_crossing() {
        // 11:00 PM EDT (New York) on March 17 = 03:00 UTC March 18 = 12:00 PM JST March 18
        let source_date = NaiveDate::from_ymd_opt(2026, 3, 17).unwrap();
        let result = convert_time(
            23,
            0,
            source_date,
            Tz::America__New_York,
            Tz::Asia__Tokyo,
        )
        .unwrap();
        assert_eq!(result.hour12, 12);
        assert!(result.is_pm);
        assert_eq!(result.tz_abbr, "JST");
        assert!(result.date_changed);
        assert_eq!(result.date, NaiveDate::from_ymd_opt(2026, 3, 18).unwrap());
    }

    #[test]
    fn test_current_time_in_timezone() {
        let now_utc = Utc.with_ymd_and_hms(2026, 3, 17, 14, 0, 0).unwrap();
        let result = current_time_in(Tz::America__New_York, now_utc);
        // March 17 2026 14:00 UTC, NY is EDT (UTC-4) = 10:00 AM
        assert_eq!(result.hour12, 10);
        assert_eq!(result.minute, 0);
        assert!(!result.is_pm);
        assert_eq!(result.tz_abbr, "EDT");
    }

    #[test]
    fn test_format_zoned_time_no_date_change() {
        let result = ZonedTimeResult {
            hour12: 6,
            minute: 0,
            is_pm: false,
            tz_abbr: "GMT".to_string(),
            date: NaiveDate::from_ymd_opt(2026, 3, 17).unwrap(),
            date_changed: false,
        };
        assert_eq!(
            format_zoned_time(&result, crate::datetime::date_math::DateFormat::US),
            "6:00 AM GMT"
        );
    }

    #[test]
    fn test_format_zoned_time_with_date_change() {
        let result = ZonedTimeResult {
            hour12: 12,
            minute: 0,
            is_pm: true,
            tz_abbr: "JST".to_string(),
            date: NaiveDate::from_ymd_opt(2026, 3, 18).unwrap(),
            date_changed: true,
        };
        assert_eq!(
            format_zoned_time(&result, crate::datetime::date_math::DateFormat::US),
            "12:00 PM JST (March 18, 2026)"
        );
        assert_eq!(
            format_zoned_time(&result, crate::datetime::date_math::DateFormat::EU),
            "12:00 PM JST (18 March 2026)"
        );
    }
}
