use aerocontext::{Briefing, GatherOutcome, Product, ProviderError};
use chrono::{TimeZone, Utc};

use super::*;

fn params(airport: &str, radius_nm: Option<f64>, products: Option<Vec<&str>>) -> AreaBriefParams {
    AreaBriefParams {
        airport: airport.to_owned(),
        radius_nm,
        products: products.map(|names| names.into_iter().map(str::to_owned).collect()),
        etd: None,
    }
}

#[test]
fn defaults_radius_and_products() {
    let request = validate(params("ksfo", None, None)).unwrap();
    assert_eq!(request.airport, "KSFO");
    assert_eq!(request.radius_nm, DEFAULT_RADIUS_NM);
    assert_eq!(request.products, vec![ProductKind::Metar]);
}

#[test]
fn clamps_radius_into_bounds() {
    let low = validate(params("KSFO", Some(0.5), None)).unwrap();
    assert_eq!(low.radius_nm, MIN_RADIUS_NM);
    let high = validate(params("KSFO", Some(500.0), None)).unwrap();
    assert_eq!(high.radius_nm, MAX_RADIUS_NM);
    let in_range = validate(params("KSFO", Some(42.0), None)).unwrap();
    assert_eq!(in_range.radius_nm, 42.0);
}

#[test]
fn rejects_non_finite_radius() {
    for radius in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
        let error = validate(params("KSFO", Some(radius), None)).unwrap_err();
        assert!(matches!(error, ToolError::InvalidParam { .. }));
    }
}

#[test]
fn rejects_blank_airport() {
    let error = validate(params("   ", None, None)).unwrap_err();
    assert!(matches!(error, ToolError::InvalidParam { .. }));
}

#[test]
fn parses_products_case_insensitively_and_dedupes() {
    let request = validate(params("KSFO", None, Some(vec!["TAF", "sigmet", "taf"]))).unwrap();
    assert_eq!(
        request.products,
        vec![ProductKind::Taf, ProductKind::Sigmet]
    );
}

#[test]
fn rejects_unknown_product() {
    let error = validate(params("KSFO", None, Some(vec!["notam"]))).unwrap_err();
    let ToolError::InvalidParam { message } = error else {
        panic!("expected InvalidParam, got {error:?}");
    };
    assert!(message.contains("notam"));
    assert!(message.contains("metar"));
}

#[test]
fn empty_product_list_falls_back_to_metar() {
    let request = validate(params("KSFO", None, Some(vec![]))).unwrap();
    assert_eq!(request.products, vec![ProductKind::Metar]);
}

#[test]
fn shapes_products_per_source_with_attribution() {
    let request = ValidatedAreaBrief {
        airport: "KSFO".to_owned(),
        radius_nm: 30.0,
        products: vec![ProductKind::Metar, ProductKind::Taf],
        departure_at: None,
    };
    let issued = Utc.with_ymd_and_hms(2026, 6, 12, 14, 56, 0).unwrap();
    let briefing = Briefing::new("awc").with_products(vec![
        Product::new(
            ProductKind::Metar,
            "KSFO 121456Z 28012KT 10SM FEW012 17/11 A3012",
        )
        .with_location(Some("KSFO".to_owned()))
        .with_issued_at(Some(issued)),
        Product::new(
            ProductKind::Taf,
            "TAF KSFO 121120Z 1212/1318 28010KT P6SM SKC",
        ),
    ]);
    let outcomes = vec![
        GatherOutcome {
            source: "awc".to_owned(),
            result: Ok(briefing),
        },
        GatherOutcome {
            source: "leidos".to_owned(),
            result: Err(ProviderError::Unsupported {
                reason: "identifier areas need navdata".to_owned(),
            }),
        },
    ];
    let report = shape_report(&request, &outcomes, vec!["a note".to_owned()]);

    assert_eq!(report.airport, "KSFO");
    assert_eq!(report.radius_nm, 30.0);
    assert_eq!(report.products_requested, vec!["metar", "taf"]);
    assert_eq!(report.notes, vec!["a note"]);

    assert_eq!(report.products.len(), 2);
    assert!(report.products.iter().all(|p| p.source == "awc"));
    assert_eq!(report.products[0].kind, "metar");
    assert_eq!(report.products[0].location.as_deref(), Some("KSFO"));
    assert_eq!(
        report.products[0].issued_at.as_deref(),
        Some("2026-06-12T14:56:00+00:00")
    );
    assert!(report.products[0].raw_text.starts_with("KSFO 121456Z"));
    assert_eq!(report.products[1].kind, "taf");
    assert!(report.products[1].issued_at.is_none());

    assert_eq!(report.sources.len(), 2);
    assert_eq!(report.sources[0].source, "awc");
    assert_eq!(report.sources[0].products, 2);
    assert!(report.sources[0].error.is_none());
    assert_eq!(report.sources[1].source, "leidos");
    assert_eq!(report.sources[1].products, 0);
    assert!(
        report.sources[1]
            .error
            .as_deref()
            .unwrap()
            .contains("identifier areas need navdata")
    );
}
