//! Every capability future is `Send`, so a provider can be driven on a
//! multi-threaded runtime or handed to an in-process / FFI consumer. These
//! assertions pin the `#[async_trait]` (not `?Send`) contract at compile time:
//! were any capability trait made `?Send`, its boxed future would lose `Send`
//! and `assert_send` would fail to typecheck.

use std::sync::Arc;

use async_trait::async_trait;
use chrono::{TimeDelta, Utc};

use crate::{
    Area, AreaBriefingRequest, Briefing, CommandAck, ContextProvider, ControlCommand,
    ControlCommandProvider, GeoPoint, NotamProvider, NotamQuery, NotamRecord, NotamScope,
    OwnshipStatus, OwnshipStatusProvider, ProviderError, ProviderErrorKind, RouteAlert,
    RouteAlertProvider, RouteAlertQuery, RouteBriefingRequest, RouteWaypoint, Tfr, TfrProvider,
    WeatherBriefingProvider,
};

/// A provider implementing every capability; its futures are only type-checked,
/// never polled.
struct AllCapabilities;

impl ContextProvider for AllCapabilities {
    fn name(&self) -> &str {
        "all-capabilities"
    }
}

#[async_trait]
impl WeatherBriefingProvider for AllCapabilities {
    async fn area_briefing(
        &self,
        _request: &AreaBriefingRequest,
    ) -> Result<Briefing, ProviderError> {
        Ok(Briefing::new(self.name()))
    }
}

#[async_trait]
impl TfrProvider for AllCapabilities {
    async fn tfrs(&self) -> Result<Vec<Tfr>, ProviderError> {
        Ok(Vec::new())
    }
}

#[async_trait]
impl RouteAlertProvider for AllCapabilities {
    async fn route_alerts(
        &self,
        _query: &RouteAlertQuery,
    ) -> Result<Vec<RouteAlert>, ProviderError> {
        Ok(Vec::new())
    }
}

#[async_trait]
impl NotamProvider for AllCapabilities {
    async fn notams(&self, _query: &NotamQuery) -> Result<Vec<NotamRecord>, ProviderError> {
        Ok(Vec::new())
    }
}

#[async_trait]
impl OwnshipStatusProvider for AllCapabilities {
    async fn ownship_status(&self) -> Result<OwnshipStatus, ProviderError> {
        Ok(OwnshipStatus::default())
    }
}

#[async_trait]
impl ControlCommandProvider for AllCapabilities {
    async fn submit_command(&self, _command: &ControlCommand) -> Result<CommandAck, ProviderError> {
        Ok(CommandAck::default())
    }
}

/// Consuming the value proves `T: Send`; the wrapped futures are never polled.
fn assert_send<T: Send>(value: T) -> T {
    value
}

#[test]
fn every_capability_future_is_send() {
    let provider = Arc::new(AllCapabilities);

    let weather: Arc<dyn WeatherBriefingProvider> = provider.clone();
    let area = AreaBriefingRequest::new(Area::LocationRadius {
        ident: "KSFO".to_owned(),
        radius_nm: 30.0,
    });
    let route = RouteBriefingRequest::new(
        vec![
            RouteWaypoint::new(GeoPoint {
                lat: 37.6,
                lon: -122.4,
            }),
            RouteWaypoint::new(GeoPoint {
                lat: 34.0,
                lon: -118.2,
            }),
        ],
        25.0,
    );
    let _area_future = assert_send(weather.area_briefing(&area));
    let _route_future = assert_send(weather.route_briefing(&route));

    let tfr: Arc<dyn TfrProvider> = provider.clone();
    let _tfr_future = assert_send(tfr.tfrs());

    let alerts: Arc<dyn RouteAlertProvider> = provider.clone();
    let alert_query = RouteAlertQuery::new("KEWR", "KDFW", Utc::now(), TimeDelta::hours(2));
    let _alert_future = assert_send(alerts.route_alerts(&alert_query));

    let notam: Arc<dyn NotamProvider> = provider.clone();
    let notam_query = NotamQuery::new(NotamScope::Location("KBOS".to_owned()));
    let _notam_future = assert_send(notam.notams(&notam_query));

    let ownship: Arc<dyn OwnshipStatusProvider> = provider.clone();
    let _ownship_future = assert_send(ownship.ownship_status());

    let command: Arc<dyn ControlCommandProvider> = provider;
    let control = ControlCommand {};
    let _command_future = assert_send(command.submit_command(&control));
}

#[test]
fn error_kinds_map_and_serialize_kebab_case() {
    let auth = ProviderError::AuthRejected {
        context: "fetching NOTAMs".to_owned(),
        status: 403,
    };
    assert_eq!(auth.kind(), ProviderErrorKind::AuthRejected);
    assert_eq!(auth.kind().as_str(), "auth-rejected");
    // The serde form and as_str must never drift apart: agents key on both.
    for kind in [
        ProviderErrorKind::Transport,
        ProviderErrorKind::Provider,
        ProviderErrorKind::AuthRejected,
        ProviderErrorKind::Decode,
        ProviderErrorKind::Unsupported,
    ] {
        let wire = serde_json::to_value(kind).expect("serializes");
        assert_eq!(wire, kind.as_str());
    }
}
