//! `aerocontext` — unified entry point for the aeronautical-context system.
//!
//! `brief` takes an airport identifier and builds a `LocationRadius`
//! request. Leidos serves it by identifier directly; AWC briefs by
//! coordinate, so pass `--navdata <blob.acnav>` (a cycle-stamped snapshot
//! built or synced by `aerocontext-navdata`) to resolve the identifier for
//! it. `--product` narrows the request — one product kind means exactly
//! one AWC request.
//!
//! ```sh
//! aerocontext brief KSFO
//! aerocontext brief KSFO --navdata "$(aerocontext-navdata current)" --product metar
//! ```

use std::path::PathBuf;
use std::sync::Arc;

use aerocontext::{
    Area, AreaBriefingRequest, AwcClient, AwcConfig, LeidosClient, LeidosConfig,
    PRODUCTION_ENDPOINT, ProductKind, WeatherBriefingProvider, compare, gather,
    successful_briefings,
};

const DEFAULT_AIRPORT: &str = "KSFO";
const DEFAULT_RADIUS_NM: f64 = 30.0;

struct BriefArgs {
    airport: String,
    radius_nm: f64,
    navdata: Option<PathBuf>,
    products: Vec<ProductKind>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = std::env::args().skip(1);
    match args.next().as_deref() {
        Some("brief") => brief(args).await,
        Some("-h" | "--help") | None => {
            print_help();
            Ok(())
        }
        Some(other) => {
            print_help();
            Err(format!("unknown command {other:?}").into())
        }
    }
}

async fn brief(args: impl Iterator<Item = String>) -> Result<(), Box<dyn std::error::Error>> {
    let Some(args) = parse_args(args)? else {
        return Ok(()); // --help was printed
    };

    println!(
        "Area briefing — {}, {:.0} NM radius\n",
        args.airport, args.radius_nm
    );

    let mut awc = AwcClient::new(AwcConfig::default())?;
    // Explicit --navdata wins; otherwise the synced store's blob for today
    // is picked up automatically.
    let navdata_path = match &args.navdata {
        Some(path) => Some(path.clone()),
        None => {
            let store = aerocontext_navdata::sync::default_store();
            let today = chrono::Utc::now().date_naive();
            aerocontext_navdata::sync::current_blob(&store, today)
                .await
                .ok()
                .flatten()
        }
    };
    match &navdata_path {
        Some(path) => {
            let snapshot = aerocontext_navdata::blob::decode(&std::fs::read(path)?)?;
            println!(
                "navdata: {} points, cycle {} .. {} ({})",
                snapshot.points.len(),
                snapshot.cycle.effective_on,
                snapshot.cycle.next_effective_on,
                path.display(),
            );
            awc = awc.with_nav_data(snapshot);
        }
        None => {
            println!("navdata: none (run `aerocontext-navdata sync` to enable AWC ident areas)")
        }
    }
    if !args.products.is_empty() {
        println!(
            "products: {:?} ({} AWC request{})",
            args.products,
            args.products.len(),
            if args.products.len() == 1 { "" } else { "s" },
        );
    }
    println!();

    let mut providers: Vec<Arc<dyn WeatherBriefingProvider>> = vec![Arc::new(awc)];
    let leidos_note = register_leidos(&mut providers);

    let request = AreaBriefingRequest {
        area: Area::LocationRadius {
            ident: args.airport,
            radius_nm: args.radius_nm,
        },
        products: args.products,
        lookback_hours: None,
        departure_at: None,
    };

    let outcomes = gather(&providers, &request).await;
    for outcome in &outcomes {
        match &outcome.result {
            Ok(briefing) => {
                println!("{} — {} products", outcome.source, briefing.products.len());
                for product in &briefing.products {
                    println!(
                        "  {:<6} {:<5} | {}",
                        format!("{:?}", product.kind),
                        product.location.as_deref().unwrap_or("-"),
                        product.raw_text,
                    );
                }
            }
            Err(error) => println!("{} — {error}", outcome.source),
        }
    }
    if let Some(note) = leidos_note {
        println!("{note}");
    }

    // Reconcile across sources once more than one answered.
    let briefings = successful_briefings(&outcomes);
    if briefings.len() > 1 {
        println!("\n--- cross-source ---");
        print!("{}", compare(&briefings));
    }

    Ok(())
}

/// Parse `[AIRPORT] [--radius NM] [--navdata FILE] [--product KIND]...`.
/// Returns `None` when `--help` was handled.
fn parse_args(
    mut args: impl Iterator<Item = String>,
) -> Result<Option<BriefArgs>, Box<dyn std::error::Error>> {
    let mut parsed = BriefArgs {
        airport: DEFAULT_AIRPORT.to_owned(),
        radius_nm: DEFAULT_RADIUS_NM,
        navdata: None,
        products: Vec::new(),
    };
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-h" | "--help" => {
                print_help();
                return Ok(None);
            }
            "--radius" => {
                let value = args.next().ok_or("--radius needs a value in NM")?;
                parsed.radius_nm = value
                    .parse()
                    .map_err(|_| format!("invalid --radius {value:?}"))?;
            }
            "--navdata" => {
                let value = args.next().ok_or("--navdata needs a file path")?;
                parsed.navdata = Some(PathBuf::from(value));
            }
            "--product" => {
                let value = args.next().ok_or("--product needs a kind")?;
                parsed.products.push(parse_product(&value)?);
            }
            flag if flag.starts_with('-') => {
                return Err(format!("unknown option {flag:?}; try --help").into());
            }
            ident => parsed.airport = ident.to_uppercase(),
        }
    }
    Ok(Some(parsed))
}

fn parse_product(value: &str) -> Result<ProductKind, Box<dyn std::error::Error>> {
    Ok(match value.to_ascii_lowercase().as_str() {
        "metar" => ProductKind::Metar,
        "taf" => ProductKind::Taf,
        "pirep" => ProductKind::Pirep,
        "sigmet" => ProductKind::Sigmet,
        other => return Err(format!("unknown product {other:?}").into()),
    })
}

/// Register a live Leidos provider when credentials are set; otherwise return
/// a note explaining why it is unavailable.
fn register_leidos(providers: &mut Vec<Arc<dyn WeatherBriefingProvider>>) -> Option<String> {
    match std::env::var("LEIDOS_VENDOR_ID") {
        Ok(id) if !id.trim().is_empty() => {
            let config = LeidosConfig {
                endpoint: PRODUCTION_ENDPOINT.to_owned(),
                vendor_id: id,
                vendor_password: std::env::var("LEIDOS_VENDOR_PASSWORD").unwrap_or_default(),
            };
            match LeidosClient::new(config) {
                Ok(client) => {
                    providers.push(Arc::new(client));
                    None
                }
                Err(error) => Some(format!("leidos — unavailable ({error})")),
            }
        }
        _ => Some("leidos — unavailable (set LEIDOS_VENDOR_ID to enable)".to_owned()),
    }
}

fn print_help() {
    println!("aerocontext — pluggable aeronautical context");
    println!();
    println!("USAGE:");
    println!("    aerocontext <COMMAND>");
    println!();
    println!("COMMANDS:");
    println!("    brief [AIRPORT] [--radius NM] [--navdata FILE] [--product KIND]...");
    println!("        area weather briefing across the registered providers");
    println!();
    println!("ARGS:");
    println!("    AIRPORT           ICAO identifier, e.g. KSFO (default: {DEFAULT_AIRPORT})");
    println!();
    println!("OPTIONS:");
    println!(
        "    --radius <NM>     briefing radius in nautical miles (default: {DEFAULT_RADIUS_NM:.0})"
    );
    println!("    --navdata <FILE>  .acnav snapshot for AWC identifier resolution");
    println!("    --product <KIND>  metar|taf|pirep|sigmet; repeatable. One kind = one AWC");
    println!("                      request (default: provider's full set, 4 AWC requests)");
    println!("    -h, --help        print this help");
    println!();
    println!("ENV:");
    println!("    LEIDOS_VENDOR_ID / LEIDOS_VENDOR_PASSWORD   enable the Leidos source");
    println!();
    println!("NOTE: AWC briefs by coordinate; without --navdata it reports an identifier");
    println!("      request unsupported (Leidos resolves identifiers natively).");
    println!("      Get snapshots with `aerocontext-navdata sync` / `current`.");
}
