use aerocontext_core::GeoPoint;
use quick_xml::Reader;
use quick_xml::events::Event;

use super::*;

fn params(location: &str) -> AreaBriefingParams<'_> {
    AreaBriefingParams {
        location_identifier: location,
        departure: "2026-06-05T14:30:00Z".parse().expect("valid timestamp"),
        radius_nm: 25,
    }
}

/// Collect `(element local name, text)` pairs in document order.
fn elements(xml: &str) -> Vec<(String, String)> {
    let mut reader = Reader::from_str(xml);
    let mut out: Vec<(String, String)> = Vec::new();
    loop {
        match reader.read_event().expect("well-formed XML") {
            Event::Eof => break,
            Event::Start(e) | Event::Empty(e) => out.push((
                String::from_utf8_lossy(e.local_name().as_ref()).into_owned(),
                String::new(),
            )),
            Event::Text(t) => {
                if let Some(last) = out.last_mut() {
                    last.1.push_str(&t.xml_content().expect("valid text"));
                }
            }
            _ => {}
        }
    }
    out
}

#[test]
fn envelope_children_follow_wsdl_sequence_order() {
    let xml = area_briefing_envelope(&params("KIAD"));
    let parsed = elements(&xml);
    let names: Vec<&str> = parsed.iter().map(|(n, _)| n.as_str()).collect();
    assert_eq!(
        names,
        [
            "Envelope",
            "Header",
            "Body",
            "AreaBriefingRequest",
            "includeCodedMessages",
            "notABriefing",
            "versionRequested",
            "briefingType",
            "fixName",
            "locationIdentifier",
            "departureInstant",
            "briefingRadius",
        ],
        "AreaBriefingRequest is an xs:sequence — order is part of the contract"
    );
}

#[test]
fn envelope_carries_documented_values() {
    let xml = area_briefing_envelope(&params("KIAD"));
    let by_name = elements(&xml);
    let text = |name: &str| -> &str {
        by_name
            .iter()
            .find(|(n, _)| n == name)
            .map(|(_, t)| t.as_str())
            .unwrap_or_default()
    };
    assert_eq!(text("includeCodedMessages"), "true");
    assert_eq!(
        text("notABriefing"),
        "true",
        "data-only call: no pilot association"
    );
    assert_eq!(text("versionRequested"), "20160225");
    assert_eq!(text("briefingType"), "SIMPLE");
    assert_eq!(text("locationIdentifier"), "KIAD");
    assert_eq!(text("departureInstant"), "2026-06-05T14:30:00Z");
    assert_eq!(text("briefingRadius"), "25");
}

#[test]
fn envelope_declares_soap11_and_schema_namespaces() {
    let xml = area_briefing_envelope(&params("KIAD"));
    assert!(xml.contains(r#"xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/""#));
    assert!(xml.contains(r#"xmlns:lfs="https://lmfsweb.afss.com/Website/schemas""#));
}

#[test]
fn location_text_is_xml_escaped() {
    let xml = area_briefing_envelope(&params("A&B<C>"));
    assert!(xml.contains("<lfs:locationIdentifier>A&amp;B&lt;C&gt;</lfs:locationIdentifier>"));
}

#[test]
fn ddmm_formats_and_pads_hemispheres() {
    // 38°57'N 077°27'W — the schema's documented DDMM(N|S)DDDMM(E|W) form.
    let dc = GeoPoint {
        lat: 38.95,
        lon: -77.45,
    };
    assert_eq!(ddmm_location(dc), "3857N07727W");

    let oslo = GeoPoint {
        lat: 60.0,
        lon: 10.0,
    };
    assert_eq!(ddmm_location(oslo), "6000N01000E");

    let southern = GeoPoint {
        lat: -33.95,
        lon: 18.6,
    };
    assert_eq!(ddmm_location(southern), "3357S01836E");

    let origin = GeoPoint { lat: 0.0, lon: 0.0 };
    assert_eq!(ddmm_location(origin), "0000N00000E");
}

#[test]
fn ddmm_minute_rounding_carries_into_degrees() {
    let nearly_39 = GeoPoint {
        lat: 38.9999,
        lon: -77.9999,
    };
    assert_eq!(ddmm_location(nearly_39), "3900N07800W");
}

#[test]
fn ddmm_extremes_stay_in_pattern() {
    let pole = GeoPoint {
        lat: 90.0,
        lon: 180.0,
    };
    assert_eq!(ddmm_location(pole), "9000N18000E");
}
