// Example showcasing the experimental introspection feature.
// Run with: `cargo run --features experimental-introspection --example introspection`

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    #[cfg(feature = "experimental-introspection")]
    {
        use actix_web::{dev::Service, guard, web, App, HttpResponse, HttpServer, Responder};
        use serde::Deserialize;

        // Initialize logging
        env_logger::Builder::new()
            .filter_level(log::LevelFilter::Debug)
            .init();

        // Custom guard to check if the Content-Type header is present.
        struct ContentTypeGuard;

        impl guard::Guard for ContentTypeGuard {
            fn check(&self, req: &guard::GuardContext<'_>) -> bool {
                req.head()
                    .headers()
                    .contains_key(actix_web::http::header::CONTENT_TYPE)
            }
        }

        // Data structure for endpoints that receive JSON.
        #[derive(Deserialize)]
        struct UserInfo {
            username: String,
            age: u8,
        }

        // GET /introspection for JSON response
        async fn introspection_handler_json(
            tree: web::Data<actix_web::introspection::IntrospectionTree>,
        ) -> impl Responder {
            let report = tree.report_as_json();
            HttpResponse::Ok()
                .content_type("application/json")
                .body(report)
        }

        // GET /introspection/externals for external resources report
        async fn introspection_handler_externals(
            tree: web::Data<actix_web::introspection::IntrospectionTree>,
        ) -> impl Responder {
            let report = tree.report_externals_as_json();
            HttpResponse::Ok()
                .content_type("application/json")
                .body(report)
        }

        // GET /introspection for plain text response
        async fn introspection_handler_text(
            tree: web::Data<actix_web::introspection::IntrospectionTree>,
        ) -> impl Responder {
            let report = tree.report_as_text();
            HttpResponse::Ok().content_type("text/plain").body(report)
        }

        // GET /api/v1/item/{id} and GET /v1/item/{id}
        #[actix_web::get("/item/{id}")]
        async fn get_item(path: web::Path<u32>) -> impl Responder {
            let id = path.into_inner();
            HttpResponse::Ok().body(format!("Requested item with id: {}", id))
        }

        // POST /api/v1/info
        #[actix_web::post("/info")]
        async fn post_user_info(info: web::Json<UserInfo>) -> impl Responder {
            HttpResponse::Ok().json(format!(
                "User {} with age {} received",
                info.username, info.age
            ))
        }

        // /api/v1/guarded
        async fn guarded_handler() -> impl Responder {
            HttpResponse::Ok().body("Passed the Content-Type guard!")
        }

        // GET /api/v2/hello
        async fn hello_v2() -> impl Responder {
            HttpResponse::Ok().body("Hello from API v2!")
        }

        // GET /admin/dashboard
        async fn admin_dashboard() -> impl Responder {
            HttpResponse::Ok().body("Welcome to the Admin Dashboard!")
        }

        // GET /admin/settings
        async fn get_settings() -> impl Responder {
            HttpResponse::Ok().body("Current settings: ...")
        }

        // POST /admin/settings
        async fn update_settings() -> impl Responder {
            HttpResponse::Ok().body("Settings have been updated!")
        }

        // GET and POST on /
        async fn root_index() -> impl Responder {
            HttpResponse::Ok().body("Welcome to the Root Endpoint!")
        }

        // GET /alpha and /beta (named multi-pattern resource)
        async fn multi_pattern() -> impl Responder {
            HttpResponse::Ok().body("Hello from multi-pattern resource!")
        }

        // GET /acceptable (Acceptable guard)
        async fn acceptable_guarded() -> impl Responder {
            HttpResponse::Ok().body("Acceptable guard matched!")
        }

        // GET /hosted (Host guard)
        async fn host_guarded() -> impl Responder {
            HttpResponse::Ok().body("Host guard matched!")
        }

        // Additional endpoints for /extra
        fn extra_endpoints(cfg: &mut web::ServiceConfig) {
            cfg.service(
                web::scope("/extra")
                    .route(
                        "/ping",
                        web::get().to(|| async { HttpResponse::Ok().body("pong") }), // GET /extra/ping
                    )
                    .service(
                        web::resource("/multi")
                            .route(web::get().to(|| async {
                                HttpResponse::Ok().body("GET response from /extra/multi")
                            })) // GET /extra/multi
                            .route(web::post().to(|| async {
                                HttpResponse::Ok().body("POST response from /extra/multi")
                            })), // POST /extra/multi
                    )
                    .service(
                        web::scope("{entities_id:\\d+}")
                            .service(
                                web::scope("/secure")
                                    .route(
                                        "",
                                        web::get().to(|| async {
                                            HttpResponse::Ok()
                                                .body("GET response from /extra/secure")
                                        }),
                                    ) // GET /extra/{entities_id}/secure/
                                    .route(
                                        "/post",
                                        web::post().to(|| async {
                                            HttpResponse::Ok()
                                                .body("POST response from /extra/secure")
                                        }),
                                    ), // POST /extra/{entities_id}/secure/post
                            )
                            .wrap_fn(|req, srv| {
                                println!(
                                    "Request to /extra/secure with id: {}",
                                    req.match_info().get("entities_id").unwrap()
                                );
                                let fut = srv.call(req);
                                async move {
                                    let res = fut.await?;
                                    Ok(res)
                                }
                            }),
                    ),
            );
        }

        // Additional endpoints for /foo
        fn other_endpoints(cfg: &mut web::ServiceConfig) {
            cfg.service(
                web::scope("/extra")
                    .route(
                        "/ping",
                        web::post()
                            .to(|| async { HttpResponse::Ok().body("post from /extra/ping") }), // POST /foo/extra/ping
                    )
                    .route(
                        "/ping",
                        web::delete()
                            .to(|| async { HttpResponse::Ok().body("delete from /extra/ping") }), // DELETE /foo/extra/ping
                    ),
            );
        }

        // Create the HTTP server with all the routes and handlers
        let server = HttpServer::new(|| {
            App::new()
                // Get introspection report
                // curl --location '127.0.0.1:8080/introspection' --header 'Accept: application/json'
                // curl --location '127.0.0.1:8080/introspection' --header 'Accept: text/plain'
                // curl --location '127.0.0.1:8080/introspection/externals'
                .external_resource("app-external", "https://example.com/{id}")
                .service(
                    web::resource("/introspection")
                        .route(
                            web::get()
                                .guard(guard::Header("accept", "application/json"))
                                .to(introspection_handler_json),
                        )
                        .route(
                            web::get()
                                .guard(guard::Header("accept", "text/plain"))
                                .to(introspection_handler_text),
                        ),
                )
                .service(
                    web::resource("/introspection/externals")
                        .route(web::get().to(introspection_handler_externals)),
                )
                .service(
                    web::resource(["/alpha", "/beta"])
                        .name("multi")
                        .route(web::get().to(multi_pattern)),
                )
                .route(
                    "/acceptable",
                    web::get()
                        .guard(guard::Acceptable::new(mime::APPLICATION_JSON).match_star_star())
                        .to(acceptable_guarded),
                )
                .route(
                    "/hosted",
                    web::get().guard(guard::Host("127.0.0.1")).to(host_guarded),
                )
                // API endpoints under /api
                .service(
                    web::scope("/api")
                        .configure(|cfg| {
                            cfg.external_resource("api-external", "https://api.example/{id}");
                        })
                        // Endpoints under /api/v1
                        .service(
                            web::scope("/v1")
                                .service(get_item) // GET /api/v1/item/{id}
                                .service(post_user_info) // POST /api/v1/info
                                .route(
                                    "/guarded",
                                    web::route().guard(ContentTypeGuard).to(guarded_handler), // /api/v1/guarded
                                ),
                        )
                        // Endpoints under /api/v2
                        .service(web::scope("/v2").route("/hello", web::get().to(hello_v2))), // GET /api/v2/hello
                )
                // Endpoints under /v1 (outside /api)
                .service(web::scope("/v1").service(get_item)) // GET /v1/item/{id}
                // Admin endpoints under /admin
                .service(
                    web::scope("/admin")
                        .route("/dashboard", web::get().to(admin_dashboard)) // GET /admin/dashboard
                        .service(
                            web::resource("/settings")
                                .route(web::get().to(get_settings)) // GET /admin/settings
                                .route(web::post().to(update_settings)), // POST /admin/settings
                        ),
                )
                // Root endpoints
                .service(
                    web::resource("/")
                        .route(web::get().to(root_index)) // GET /
                        .route(web::post().to(root_index)), // POST /
                )
                // Endpoints under /bar
                .service(web::scope("/bar").configure(extra_endpoints)) // /bar/extra/ping, /bar/extra/multi, etc.
                // Endpoints under /foo
                .service(web::scope("/foo").configure(other_endpoints)) // /foo/extra/ping with POST and DELETE
                // Additional endpoints under /extra
                .configure(extra_endpoints) // /extra/ping, /extra/multi, etc.
                .configure(other_endpoints)
                // Endpoint that rejects GET on /not_guard (allows other methods)
                .route(
                    "/not_guard",
                    web::route()
                        .guard(guard::Not(guard::Get()))
                        .to(HttpResponse::MethodNotAllowed),
                )
                // Endpoint that requires GET with header or POST on /all_guard
                .route(
                    "/all_guard",
                    web::route()
                        .guard(
                            guard::All(guard::Get())
                                .and(guard::Header("content-type", "plain/text"))
                                .and(guard::Any(guard::Post())),
                        )
                        .to(HttpResponse::MethodNotAllowed),
                )
        })
        .workers(5)
        .bind("127.0.0.1:8080")?;

        server.run().await
    }
    #[cfg(not(feature = "experimental-introspection"))]
    {
        eprintln!("This example requires the 'experimental-introspection' feature to be enabled.");
        std::process::exit(1);
    }
}
