use chrono::NaiveDate;

use super::*;

fn date(year: i32, month: u32, day: u32) -> NaiveDate {
    NaiveDate::from_ymd_opt(year, month, day).expect("test date is valid")
}

#[test]
fn faa_nasr_cycle_spans_28_days() {
    let cycle = NavDataCycle::faa_nasr(date(2026, 5, 14)).expect("cycle builds");
    assert_eq!(cycle.authority, NavDataAuthority::FaaNasr);
    assert_eq!(cycle.effective_on, date(2026, 5, 14));
    assert_eq!(cycle.next_effective_on, date(2026, 6, 11));
    assert!(cycle.contains(date(2026, 5, 14)));
    assert!(cycle.contains(date(2026, 6, 10)));
    assert!(!cycle.contains(date(2026, 6, 11)));
}

#[test]
fn snapshot_resolves_identifiers_case_insensitively() {
    let snapshot = NavDataSnapshot::new(
        NavDataCycle::faa_nasr(date(2026, 5, 14)).expect("cycle builds"),
        vec![
            NavPoint::new(
                "KSFO",
                NavPointKind::Airport,
                GeoPoint {
                    lat: 37.6188056,
                    lon: -122.3754167,
                },
            )
            .with_name(Some("San Francisco International".to_owned())),
            NavPoint::new(
                "DUMBA",
                NavPointKind::Waypoint,
                GeoPoint {
                    lat: 37.4201667,
                    lon: -122.102,
                },
            ),
        ],
    );

    let airport = snapshot.resolve(" ksfo ").expect("airport resolves");
    assert_eq!(airport.kind, NavPointKind::Airport);
    assert_eq!(airport.name.as_deref(), Some("San Francisco International"));
    assert!(snapshot.resolve("missing").is_none());
    assert!(snapshot.contains(date(2026, 6, 1)));
}

#[test]
fn snapshot_round_trips_through_serde() {
    let snapshot = NavDataSnapshot::new(
        NavDataCycle::faa_nasr(date(2026, 5, 14)).expect("cycle builds"),
        vec![NavPoint::new(
            "OSI",
            NavPointKind::Navaid,
            GeoPoint {
                lat: 37.3925,
                lon: -122.281,
            },
        )],
    );

    let json = serde_json::to_string(&snapshot).expect("snapshot serializes");
    let decoded: NavDataSnapshot = serde_json::from_str(&json).expect("snapshot deserializes");
    assert_eq!(decoded, snapshot);
}

#[test]
fn region_preference_beats_first_match() {
    let cycle = NavDataCycle::faa_nasr(NaiveDate::from_ymd_opt(2026, 6, 11).unwrap()).unwrap();
    let snapshot = NavDataSnapshot::new(
        cycle,
        vec![
            NavPoint::new(
                "DUPFX",
                NavPointKind::Waypoint,
                GeoPoint { lat: 1.0, lon: 1.0 },
            )
            .with_region(Some("K2".to_owned())),
            NavPoint::new(
                "DUPFX",
                NavPointKind::Waypoint,
                GeoPoint { lat: 9.0, lon: 9.0 },
            )
            .with_region(Some("PA".to_owned())),
        ],
    );
    let pa = snapshot
        .resolve_preferring_region("DUPFX", Some("PA"))
        .unwrap();
    assert!((pa.position.lat - 9.0).abs() < 1e-9);
    // Unknown region falls back to the first ident match.
    let fallback = snapshot
        .resolve_preferring_region("DUPFX", Some("ZZ"))
        .unwrap();
    assert!((fallback.position.lat - 1.0).abs() < 1e-9);
}

#[test]
fn airways_attach_and_select_by_designator() {
    let cycle = NavDataCycle::faa_nasr(NaiveDate::from_ymd_opt(2026, 6, 11).unwrap()).unwrap();
    let snapshot = NavDataSnapshot::new(cycle, Vec::new()).with_airways(vec![
        Airway::new(
            "V25",
            AirwayLocation::Conus,
            vec![AirwayPoint::new("AAA"), AirwayPoint::new("BBB")],
        ),
        Airway::new("V25", AirwayLocation::Alaska, vec![AirwayPoint::new("CCC")]),
        Airway::new("J94", AirwayLocation::Conus, vec![AirwayPoint::new("DDD")]),
    ]);
    let matches: Vec<_> = snapshot.airways_named("v25").collect();
    assert_eq!(matches.len(), 2, "same ident across locations");
    assert_eq!(snapshot.airways_named("Q1").count(), 0);
    assert_eq!(AirwayLocation::Alaska.code(), "A");
}

#[test]
fn airspaces_select_by_center_airport_case_insensitively() {
    let cycle = NavDataCycle::faa_nasr(NaiveDate::from_ymd_opt(2026, 6, 11).unwrap()).unwrap();
    let snapshot = NavDataSnapshot::new(cycle, Vec::new()).with_airspaces(vec![
        Airspace::new(AirspaceKind::Controlled(ControlledClass::B), "KSEA")
            .with_center_ident(Some("KSEA".to_owned())),
        Airspace::new(AirspaceKind::Controlled(ControlledClass::D), "KSEA")
            .with_center_ident(Some("KSEA".to_owned())),
        // A SUA has no owning airport and must never match a center lookup.
        Airspace::new(AirspaceKind::Restrictive(RestrictiveKind::Moa), "BOARDMAN"),
    ]);
    assert_eq!(snapshot.airspaces_at(" ksea ").count(), 2);
    assert_eq!(
        snapshot.airspaces_at("BOARDMAN").count(),
        0,
        "SUA has no center"
    );
    assert_eq!(snapshot.airspaces_at("KJFK").count(), 0);
}
