use axum::body::Body;
use axum::http::{Request, StatusCode};
use nestrs::prelude::*;
use tower::util::ServiceExt;

#[derive(Default)]
#[injectable]
struct AppState;

#[controller(prefix = "/root", version = "v1")]
struct RootController;

impl RootController {
    #[get("/")]
    async fn root() -> &'static str {
        "root"
    }
}

impl_routes!(RootController, state AppState => [
    GET "/" with () => RootController::root,
]);

#[module(
    controllers = [RootController],
    providers = [AppState],
)]
struct RootModule;

#[derive(Default)]
#[injectable]
struct FeatureState;

#[controller(prefix = "/feature", version = "v1")]
struct FeatureController;

impl FeatureController {
    #[get("/")]
    async fn feature() -> &'static str {
        "feature"
    }
}

impl_routes!(FeatureController, state FeatureState => [
    GET "/" with () => FeatureController::feature,
]);

#[module(
    controllers = [FeatureController],
    providers = [FeatureState],
)]
struct FeatureModule;

#[tokio::test]
async fn create_with_modules_merges_dynamic_feature_router() {
    let router = NestFactory::create_with_modules::<RootModule, _>([DynamicModule::from_module::<
        FeatureModule,
    >()])
    .into_router();

    let root = router
        .clone()
        .oneshot(
            Request::builder()
                .uri("/v1/root")
                .method("GET")
                .body(Body::empty())
                .expect("request should be valid"),
        )
        .await
        .expect("router should serve request");
    assert_eq!(root.status(), StatusCode::OK);

    let feature = router
        .oneshot(
            Request::builder()
                .uri("/v1/feature")
                .method("GET")
                .body(Body::empty())
                .expect("request should be valid"),
        )
        .await
        .expect("router should serve request");
    assert_eq!(feature.status(), StatusCode::OK);
}

#[derive(Default)]
#[injectable]
struct FeatureService;

#[module(
    providers = [FeatureService],
    exports = [FeatureService],
)]
struct FeatureProvidersModule;

#[derive(Default)]
#[injectable]
struct RootState {
    feature: std::sync::Arc<FeatureService>,
}

#[module(
    imports = [DynamicModule::from_module::<FeatureProvidersModule>()],
    providers = [RootState],
)]
struct RootProvidersModule;

#[test]
fn dynamic_module_imports_can_export_providers() {
    let (registry, _) = <RootProvidersModule as Module>::build();

    let state = registry.get::<RootState>();
    let feature = registry.get::<FeatureService>();
    assert!(std::sync::Arc::ptr_eq(&state.feature, &feature));
}
