#![allow(clippy::expect_used, clippy::panic)]

use chrono::TimeZone;

use super::*;
use crate::Agreement;

fn ts(h: u32) -> DateTime<Utc> {
    Utc.with_ymd_and_hms(2026, 6, 16, h, 0, 0).unwrap()
}

/// A bare NMS record carrying `number` at `location`, with `text`.
fn nms(number: &str, location: &str, text: &str) -> NotamRecord {
    NotamRecord::new(NotamSource::FaaNms, number, location, text)
}

fn leidos(number: &str, location: &str, text: &str) -> NotamRecord {
    NotamRecord::new(NotamSource::Leidos, number, location, text)
}

#[test]
fn builders_populate_the_structured_fields() {
    let record = nms("09/186", "KATL", "RWY 09L/27R CLSD")
        .with_id(Some("1757616312849974".to_owned()))
        .with_kind(Some(NotamType::New))
        .with_classification(Some(NotamClassification::Domestic))
        .with_feature(Some(NotamFeature::Runway))
        .with_q_code(Some("QMRLC".to_owned()))
        .with_affected_fir(Some("ZTL".to_owned()))
        .with_flight_levels(Some(0), Some(999))
        .with_issued(Some(ts(10)))
        .with_valid(Some(NotamValidity::new(ts(11), None).estimated(true)));
    assert_eq!(record.feature, Some(NotamFeature::Runway));
    assert_eq!(record.valid.unwrap().end, None, "PERM has no end");
    assert!(record.valid.unwrap().estimated);
    assert_eq!(
        record.richness(),
        9,
        "id+kind+class+feature+q+fir+fl-band+issued+valid (the FL band counts once)"
    );
}

#[test]
fn a_single_source_notam_is_single_and_kept() {
    let report = reconcile(vec![nms("09/186", "KATL", "RWY 09L CLSD")]);
    assert_eq!(report.notams.len(), 1);
    assert_eq!(report.notams[0].agreement, Agreement::Single);
    assert_eq!(report.notams[0].sources, vec![NotamSource::FaaNms]);
    assert_eq!(report.single_source().count(), 1);
}

#[test]
fn duplicates_from_overlapping_queries_collapse() {
    // The same NMS NOTAM returned by two overlapping radius tiles.
    let report = reconcile(vec![
        nms("09/186", "KATL", "RWY 09L CLSD"),
        nms("09/186", "KATL", "RWY 09L CLSD"),
    ]);
    assert_eq!(report.notams.len(), 1, "one NOTAM, not two");
    assert_eq!(report.notams[0].sources, vec![NotamSource::FaaNms]);
    assert_eq!(report.notams[0].agreement, Agreement::Single);
}

#[test]
fn two_sources_that_agree_keep_the_richer_record() {
    // NMS carries structure; Leidos carries only the same text.
    let structured = nms("09/186", "KATL", "RWY 09L/27R CLSD")
        .with_feature(Some(NotamFeature::Runway))
        .with_geometry(Some(Area::PointRadius {
            center: GeoPoint {
                lat: 33.6,
                lon: -84.4,
            },
            radius_nm: 5.0,
        }));
    let report = reconcile(vec![
        structured,
        leidos("09/186", "KATL", "RWY 09L/27R CLSD"),
    ]);
    assert_eq!(report.notams.len(), 1);
    let entry = &report.notams[0];
    assert_eq!(entry.agreement, Agreement::Agree);
    assert_eq!(
        entry.sources,
        vec![NotamSource::FaaNms, NotamSource::Leidos]
    );
    // The structured NMS record wins.
    assert_eq!(entry.notam.source, NotamSource::FaaNms);
    assert!(entry.notam.geometry.is_some());
}

#[test]
fn free_text_differences_alone_are_not_a_disagreement() {
    // The (location, number) key already proves it is the same NOTAM, so
    // source-specific text framing — whitespace, the Leidos coded `!acct number
    // location` prefix, a trailing `=` — must not read as a conflict. With no
    // structured fields to compare, the two sources agree.
    let report = reconcile(vec![
        nms("09/186", "KATL", "RWY 09L CLSD"),
        leidos("09/186", "KATL", "!ATL 09/186 KATL RWY 09L CLSD ="),
    ]);
    assert_eq!(report.notams[0].agreement, Agreement::Agree);
    assert_eq!(report.disagreements().count(), 0);
}

#[test]
fn the_key_folds_case_and_leading_zeros_across_sources() {
    // KATL vs katl, 09/186 vs 9/186 — the same NOTAM in two source formats
    // must reconcile to one group, not two false `Single`s.
    let report = reconcile(vec![
        nms("09/186", "KATL", "RWY 09L CLSD"),
        leidos("9/186", "katl", "RWY 09L CLSD"),
    ]);
    assert_eq!(
        report.notams.len(),
        1,
        "case and zero-padding fold together"
    );
    assert_eq!(
        report.notams[0].sources,
        vec![NotamSource::FaaNms, NotamSource::Leidos]
    );
    assert_eq!(report.notams[0].agreement, Agreement::Agree);
}

#[test]
fn icao_and_faa_location_forms_reconcile_across_sources() {
    // FAA NMS reports the domestic id (BOS); Leidos reports the ICAO id (KBOS).
    // The same NOTAM must merge, not split into two false `Single`s.
    let report = reconcile(vec![
        nms("06/332", "BOS", "TWY B WIP"),
        leidos("06/332", "KBOS", "TWY B WIP"),
    ]);
    assert_eq!(report.notams.len(), 1, "KBOS folds to BOS");
    assert_eq!(
        report.notams[0].sources,
        vec![NotamSource::FaaNms, NotamSource::Leidos]
    );
    assert_eq!(report.notams[0].agreement, Agreement::Agree);
    // The higher-fidelity source (NMS) supplies the displayed record.
    assert_eq!(report.notams[0].notam.location, "BOS");
}

#[test]
fn a_structured_conflict_disagrees_and_retains_each_sources_record() {
    // Both sources carry validity, but the end times conflict — a real
    // disagreement a reader must see, with both records preserved. (Text alone
    // never disagrees; only a field both populate can.)
    let a = nms("09/186", "KATL", "RWY 09L CLSD")
        .with_valid(Some(NotamValidity::new(ts(10), Some(ts(12)))));
    let b = leidos("09/186", "KATL", "RWY 09L CLSD")
        .with_valid(Some(NotamValidity::new(ts(10), Some(ts(18)))));
    let report = reconcile(vec![a, b]);
    let entry = &report.notams[0];
    assert_eq!(entry.agreement, Agreement::Disagree);
    assert_eq!(entry.records.len(), 2, "both sources' records survive");
    assert_eq!(report.disagreements().count(), 1);
}

#[test]
fn the_richest_pick_is_order_independent() {
    // Equal richness, equal length, differing text: the winner must not depend
    // on input order (a total-order tiebreak, not `max_by`'s last-wins).
    let forward = reconcile(vec![
        nms("09/186", "KATL", "RWY 09L CLSD"),
        nms("09/186", "KATL", "RWY 09R CLSD"),
    ]);
    let reverse = reconcile(vec![
        nms("09/186", "KATL", "RWY 09R CLSD"),
        nms("09/186", "KATL", "RWY 09L CLSD"),
    ]);
    assert_eq!(
        forward.notams[0].notam.raw_text,
        reverse.notams[0].notam.raw_text
    );
}

#[test]
fn distinct_notams_stay_separate_and_sort_deterministically() {
    let report = reconcile(vec![
        nms("09/193", "KATL", "TWY A CLSD"),
        nms("09/186", "KATL", "RWY 09L CLSD"),
        nms("01/004", "KBOS", "OBST TOWER LGT U/S"),
    ]);
    let keys: Vec<(&str, &str)> = report
        .notams
        .iter()
        .map(|r| (r.notam.location.as_str(), r.notam.number.as_str()))
        .collect();
    assert_eq!(
        keys,
        vec![("KATL", "09/186"), ("KATL", "09/193"), ("KBOS", "01/004")]
    );
}

#[test]
fn query_builders_set_scope_and_filters() {
    let query = NotamQuery::new(NotamScope::PointRadius {
        center: GeoPoint {
            lat: 42.0,
            lon: -71.0,
        },
        radius_nm: 50.0,
    })
    .with_features(vec![NotamFeature::Runway, NotamFeature::Obstruction]);
    assert_eq!(query.features.len(), 2);
    assert!(query.classifications.is_empty());
}
