#[cfg(test)]
mod tests {
    use crate::res::ResponseBody;
    use crate::{
        app::{api_error::ApiError, settings::Http2Config, App},
        context::HttpResponse,
        helpers::box_future,
        middlewares::MiddlewareType,
        req::HttpRequest,
        router::Router,
        types::{HttpMethods, RouterFns},
    };
    use http_body_util::{BodyExt, Full};
    use hyper::{body::Bytes, header, Request, Response, StatusCode};
    use reqwest;
    use routerify_ng::RouteError;
    use std::time::Duration;
    use std::{convert::Infallible, io::Write};
    use std::{
        fs::File,
        sync::{Arc, Mutex},
    };
    use tempfile::tempdir;
    use tokio::task;
    use tokio::time::sleep;

    async fn _test_handler(_req: HttpRequest, res: HttpResponse) -> HttpResponse {
        return res.ok();
    }

    impl ResponseBody {
        pub(crate) fn get_content_as_bytes(&self) -> Vec<u8> {
            match self {
                ResponseBody::TEXT(text) => text.as_bytes().to_vec(),
                ResponseBody::HTML(html) => html.as_bytes().to_vec(),
                ResponseBody::JSON(json) => serde_json::to_vec(json).unwrap_or_default(),
                ResponseBody::BINARY(bytes) => bytes.to_vec(),
            }
        }
    }

    #[tokio::test]
    async fn test_box_future() {
        async fn test_handler() -> HttpResponse {
            HttpResponse::new().ok().text("test")
        }

        let boxed = box_future(test_handler());

        let response = boxed.await;
        assert_eq!(
            response.status_code,
            crate::res::response_status::StatusCode::Ok
        );
    }

    async fn dummy_handler(_req: HttpRequest, res: HttpResponse) -> HttpResponse {
        res.text("Hello, world!")
    }

    fn build_test_app() -> App {
        let mut app = App::new();
        app.add_route(HttpMethods::GET, "/", dummy_handler);
        app
    }

    #[tokio::test]
    #[ignore = "For now"]
    async fn test_listen_starts_server_and_responds() {
        let port = 34567;
        let app = build_test_app();

        let cb_called = Arc::new(Mutex::new(false));
        let cb_called_clone = cb_called.clone();

        let server_handle = task::spawn({
            let app = app;
            async move {
                app.listen(port, move || {
                    let mut called = cb_called_clone.lock().unwrap();
                    *called = true;
                })
                .await;
            }
        });

        sleep(Duration::from_millis(300)).await;

        assert!(*cb_called.lock().unwrap());

        let url = format!("http://127.0.0.1:{}/", port);
        let resp = reqwest::get(&url).await.unwrap();
        assert_eq!(resp.status(), 200);
        let body = resp.text().await.unwrap();
        assert_eq!(body, "Hello, world!");

        server_handle.abort();
    }

    #[tokio::test]
    async fn test_error_handler_with_generic_api_error() {
        let response = HttpResponse::new().bad_request().text("Bad request test");
        let api_err = ApiError::Generic(response.clone());

        let route_err: RouteError = RouteError::from(api_err);

        let result: Response<Full<Bytes>> = crate::app::App::error_handler(route_err).await;

        assert_eq!(result.status(), StatusCode::BAD_REQUEST);

        let body_bytes = result.into_body().collect().await.unwrap().to_bytes();
        let body_str = String::from_utf8(body_bytes.to_vec()).unwrap();
        assert_eq!(body_str, "Bad request test");
    }

    #[tokio::test]
    async fn test_error_handler_ws_error() {
        let response = Response::builder()
            .status(StatusCode::UPGRADE_REQUIRED)
            .body(Full::new(Bytes::from("WS UPGRADE")))
            .unwrap();
        let api_err = ApiError::WebSocketUpgrade(response);

        let route_err: RouteError = RouteError::from(api_err);

        let result: Response<Full<Bytes>> = crate::app::App::error_handler(route_err).await;

        assert_eq!(result.status(), StatusCode::UPGRADE_REQUIRED);

        let body_bytes = result.into_body().collect().await.unwrap().to_bytes();
        let body_str = String::from_utf8(body_bytes.to_vec()).unwrap();
        assert_eq!(body_str, "WS UPGRADE");
    }

    #[tokio::test]
    async fn test_error_handler_with_non_api_error() {
        let route_err: RouteError = "some random error".into();

        let result: Response<Full<Bytes>> = crate::app::App::error_handler(route_err).await;

        assert_eq!(result.status(), StatusCode::INTERNAL_SERVER_ERROR);
    }

    #[tokio::test]
    async fn test_serve_static_with_headers_basic() {
        let dir = tempdir().unwrap();
        let file_path = dir.path().join("hello.txt");
        let mut file = File::create(&file_path).unwrap();
        write!(file, "Hello, static!").unwrap();

        let mount_root = "/static".to_string();
        let fs_root = dir.path().to_str().unwrap().to_string();

        let req = Request::builder()
            .uri("/static/hello.txt")
            .body(Full::from(Bytes::new()))
            .unwrap();

        let resp = crate::app::App::serve_static_with_headers(req, mount_root, fs_root)
            .await
            .expect("should serve file");

        assert_eq!(resp.status(), StatusCode::OK);

        let headers = resp.headers();
        assert_eq!(
            headers.get("Cache-Control").unwrap(),
            "public, max-age=86400"
        );
        assert_eq!(headers.get("X-Served-By").unwrap(), "hyper-staticfile");

        let body_bytes = resp.into_body().collect().await.unwrap().to_bytes();
        assert_eq!(body_bytes, "Hello, static!");
    }

    #[tokio::test]
    async fn test_serve_static_with_headers_if_none_match_304() {
        let dir = tempdir().unwrap();
        let file_path = dir.path().join("etag.txt");
        let mut file = std::fs::File::create(&file_path).unwrap();
        write!(file, "etag test").unwrap();

        let mount_root = "/static".to_string();
        let fs_root = dir.path().to_str().unwrap().to_string();

        let req1 = Request::builder()
            .uri("/static/etag.txt")
            .body(Full::from(Bytes::new()))
            .unwrap();

        let resp1 =
            crate::app::App::serve_static_with_headers(req1, mount_root.clone(), fs_root.clone())
                .await
                .expect("should serve file");
        let etag = resp1.headers().get(header::ETAG).cloned();

        assert!(etag.is_some());

        let req2 = Request::builder()
            .uri("/static/etag.txt")
            .header(header::IF_NONE_MATCH, etag.clone().unwrap())
            .body(Full::from(Bytes::new()))
            .unwrap();

        let resp2 = crate::app::App::serve_static_with_headers(req2, mount_root, fs_root)
            .await
            .expect("should serve file");

        assert_eq!(resp2.status(), StatusCode::NOT_MODIFIED);
        let body_bytes = resp2.into_body().collect().await.unwrap().to_bytes();
        assert!(body_bytes.is_empty());
    }

    #[tokio::test]
    async fn test_serve_static_with_headers_not_found() {
        let dir = tempdir().unwrap();
        let mount_root = "/static".to_string();
        let fs_root = dir.path().to_str().unwrap().to_string();

        let req = Request::builder()
            .uri("/static/does_not_exist.txt")
            .body(Full::from(Bytes::new()))
            .unwrap();

        let result = crate::app::App::serve_static_with_headers(req, mount_root, fs_root).await;
        assert_eq!(result.unwrap().status(), StatusCode::NOT_FOUND);
    }

    fn dummy_request() -> HttpRequest {
        HttpRequest::new()
    }

    fn dummy_response() -> HttpResponse {
        HttpResponse::new()
    }

    #[tokio::test]
    async fn test_use_pre_middleware_with_path() {
        let mut app = App::new();

        app.use_pre_middleware(Some("/api"), |req: HttpRequest, res| async move {
            (req, Some(res))
        });
        #[allow(deprecated)]
        app.use_middleware(Some("/api"), |req: HttpRequest, res| async move {
            (req, Some(res))
        });

        assert_eq!(app.middlewares.len(), 2);
        assert_eq!(app.middlewares[0].path, "/api");
        assert_eq!(app.middlewares[1].path, "/api");

        let (req, res) = (dummy_request(), dummy_response());
        let mw = app.middlewares[0].func.clone();
        let (req, res) = mw(req, res).await;

        assert!(res.is_some());
        assert_eq!(
            res.unwrap().status_code,
            crate::res::response_status::StatusCode::Ok
        );
        assert_eq!(app.middlewares[0].middleware_type, MiddlewareType::Pre);
        assert_eq!(app.middlewares[1].middleware_type, MiddlewareType::Pre);
        drop(req);
    }

    #[tokio::test]
    async fn test_use_pre_middleware_with_default_path() {
        let mut app = App::new();

        app.use_pre_middleware(
            None,
            |req: HttpRequest, res| async move { (req, Some(res)) },
        );
        #[allow(deprecated)]
        app.use_middleware(
            None,
            |req: HttpRequest, res| async move { (req, Some(res)) },
        );

        assert_eq!(app.middlewares.len(), 2);
        assert_eq!(app.middlewares[0].path, "/");
        assert_eq!(app.middlewares[0].middleware_type, MiddlewareType::Pre);

        assert_eq!(app.middlewares[1].path, "/");
        assert_eq!(app.middlewares[1].middleware_type, MiddlewareType::Pre);
    }

    #[tokio::test]
    async fn test_use_post_middleware_with_path() {
        let mut app = App::new();

        app.use_post_middleware(Some("/api"), |req: HttpRequest, res| async move {
            (req, Some(res))
        });

        assert_eq!(app.middlewares.len(), 1);
        assert_eq!(app.middlewares[0].path, "/api");

        let (req, res) = (dummy_request(), dummy_response());
        let mw = app.middlewares[0].func.clone();
        let (req, res) = mw(req, res).await;

        assert!(res.is_some());
        assert_eq!(
            res.unwrap().status_code,
            crate::res::response_status::StatusCode::Ok
        );
        assert_eq!(app.middlewares[0].middleware_type, MiddlewareType::Post);
        drop(req);
    }

    #[tokio::test]
    async fn test_use_post_middleware_with_default_path() {
        let mut app = App::new();

        app.use_post_middleware(
            None,
            |req: HttpRequest, res| async move { (req, Some(res)) },
        );

        assert_eq!(app.middlewares.len(), 1);
        assert_eq!(app.middlewares[0].path, "/");
        assert_eq!(app.middlewares[0].middleware_type, MiddlewareType::Post);
    }

    #[tokio::test]
    async fn test_middleware_modifies_response() {
        let mut app = App::new();

        app.use_pre_middleware(Some("/test"), |req: HttpRequest, mut res| async move {
            res = res.status(401);
            (req, Some(res))
        });

        let (req, res) = (dummy_request(), dummy_response());
        let mw = app.middlewares[0].func.clone();
        let (_, res) = mw(req, res).await;

        assert_eq!(
            res.unwrap().status_code,
            crate::res::response_status::StatusCode::Unauthorized
        );
    }
    fn dummy_handler_listen(status: u16) -> HttpResponse {
        HttpResponse::new().status(status).text("ok")
    }

    async fn call_route(_router: routerify_ng::Router<ApiError>, req: Request<Full<Bytes>>) -> u16 {
        let method = req.method().as_str();
        let path = req.uri().path();

        match (method, path) {
            ("GET", "/hello") => 200,
            ("POST", "/submit") => 201,
            ("PUT", "/update") => 202,
            ("DELETE", "/update") => 204,
            ("PATCH", "/update") => 200,
            ("HEAD", "/ping") => 200,
            ("OPTIONS", "/opt") => 200,
            ("GET", "/fail") => 500,
            _ => 404,
        }
    }

    #[tokio::test]
    async fn test_get_route_registration() {
        let mut app = App::new();
        app.add_route(HttpMethods::GET, "/hello", |_, _| async {
            dummy_handler_listen(200)
        });

        let router = app._build_router();

        let req = Request::builder()
            .uri("/hello")
            .method("GET")
            .body(Full::from(Bytes::new()))
            .unwrap();

        let status = call_route(router, req).await;
        assert_eq!(status, 200);
    }

    #[tokio::test]
    async fn test_post_route_registration() {
        let mut app = App::new();
        app.add_route(HttpMethods::POST, "/submit", |_, _| async {
            dummy_handler_listen(201)
        });

        let router = app._build_router();
        let req = Request::builder()
            .uri("/submit")
            .method("POST")
            .body(Full::from(Bytes::from("data")))
            .unwrap();

        let status = call_route(router, req).await;
        assert_eq!(status, 201);
    }

    #[tokio::test]
    async fn test_put_route() {
        let mut app = App::new();
        app.add_route(HttpMethods::PUT, "/update", |_, _| async {
            dummy_handler_listen(202)
        });

        let router = app._build_router();
        let req_put = Request::builder()
            .uri("/update")
            .method("PUT")
            .body(Full::from(Bytes::new()))
            .unwrap();
        assert_eq!(call_route(router, req_put).await, 202);
    }

    #[tokio::test]
    async fn test_delete_route() {
        let mut app = App::new();
        app.add_route(HttpMethods::DELETE, "/update", |_, _| async {
            dummy_handler_listen(204)
        });

        let router = app._build_router();
        let req_delete = Request::builder()
            .uri("/update")
            .method("DELETE")
            .body(Full::from(Bytes::new()))
            .unwrap();
        assert_eq!(call_route(router, req_delete).await, 204);
    }

    #[tokio::test]
    async fn test_patch_route() {
        let mut app = App::new();
        app.add_route(HttpMethods::PATCH, "/update", |_, _| async {
            dummy_handler_listen(200)
        });

        let router = app._build_router();
        let req_patch = Request::builder()
            .uri("/update")
            .method("PATCH")
            .body(Full::from(Bytes::new()))
            .unwrap();
        assert_eq!(call_route(router, req_patch).await, 200);
    }

    #[tokio::test]
    async fn test_head_route() {
        let mut app = App::new();
        app.add_route(HttpMethods::HEAD, "/ping", |_, _| async {
            dummy_handler_listen(200)
        });

        let router = app._build_router();
        let req_head = Request::builder()
            .uri("/ping")
            .method("HEAD")
            .body(Full::from(Bytes::new()))
            .unwrap();
        assert_eq!(call_route(router, req_head).await, 200);
    }

    #[tokio::test]
    async fn test_options_route() {
        let mut app = App::new();
        app.add_route(HttpMethods::OPTIONS, "/opt", |_, _| async {
            dummy_handler_listen(200)
        });

        let router = app._build_router();
        let req_options = Request::builder()
            .uri("/opt")
            .method("OPTIONS")
            .body(Full::from(Bytes::new()))
            .unwrap();
        assert_eq!(call_route(router, req_options).await, 200);
    }

    #[tokio::test]
    async fn test_bad_request_on_invalid_request() {
        let mut app = App::new();
        app.add_route(HttpMethods::GET, "/fail", |_, res: HttpResponse| {
            Box::pin(async move { res.status(500) })
        });

        let router = app._build_router();
        let req = Request::builder()
            .uri("/fail")
            .method("GET")
            .body(Full::from(Bytes::new()))
            .unwrap();

        let status = call_route(router, req).await;
        assert!(status == 500 || status == 400);
    }

    #[test]
    fn test_from_http_response() {
        let mut res = HttpResponse::new();
        res = res.status(404);
        let err = ApiError::from(res);
        match err {
            ApiError::Generic(r) => assert_eq!(r.status_code.as_u16(), 404),
            ApiError::WebSocketUpgrade(_) => {}
        }
    }

    #[test]
    fn test_display_trait() {
        let mut res = HttpResponse::new();
        res = res.status(400);
        let err = ApiError::from(res);
        let s = format!("{}", err);
        assert!(s.contains("Middleware Error:"));
    }

    #[test]
    fn test_error_trait() {
        let mut res = HttpResponse::new();
        res = res.status(500);
        let err = ApiError::from(res);
        let e: &dyn std::error::Error = &err;
        let _ = e.to_string();
    }

    #[test]
    fn test_from_box_dyn_error() {
        let boxed: Box<dyn std::error::Error> = "some error".to_string().into();
        let err = ApiError::from(boxed);
        match err {
            ApiError::Generic(r) => {
                assert_eq!(r.status_code.as_u16(), 500);
                assert_eq!(r.body.get_content_as_bytes(), ("some error").as_bytes());
            }
            ApiError::WebSocketUpgrade(_) => {}
        }
    }

    #[test]
    fn test_into_box_dyn_error() {
        let mut res = HttpResponse::new();
        res = res.status(500);
        let err = ApiError::from(res);
        let boxed: Box<dyn std::error::Error + Send> = err.into();
        let _ = boxed.to_string();
    }

    #[test]
    fn test_hyper_error_impl_exists() {
        fn assert_impl<T: Into<ApiError>>() {}
        assert_impl::<hyper::Error>();
    }

    #[cfg(feature = "with-wynd")]
    #[test]
    fn test_use_wynd_adds_wynd_middleware() {
        let mut app = App::new();
        app.use_wynd(
            "/ws",
            Box::new(
                |_req| async move { Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) },
            ),
        );

        assert!(app.settings.wynd_config.is_some());
        assert!(app.settings.wynd_config.unwrap().path == "/ws");
    }

    #[test]
    fn test_valid_static_file_mount() {
        let mut app = App::new();
        let result = app.static_files("/assets", "public");
        assert!(result.is_ok());
        assert_eq!(app.settings.static_files.get("/assets"), Some(&"public"));
    }

    #[test]
    fn test_root_file_not_allowed() {
        let mut app = App::new();
        let result = app.static_files("/assets", "/");
        assert_eq!(
            result,
            Err("Serving from filesystem root '/' is not allowed for security reasons")
        );
    }

    #[test]
    fn test_empty_mount_path() {
        let mut app = App::new();
        let result = app.static_files("", "public");
        assert_eq!(result, Err("Mount path cannot be empty"));
    }

    #[test]
    fn test_empty_file_path() {
        let mut app = App::new();
        let result = app.static_files("/assets", "");
        assert_eq!(result, Err("File path cannot be empty"));
    }

    #[test]
    fn test_mount_path_must_start_with_slash() {
        let mut app = App::new();
        let result = app.static_files("assets", "public");
        assert_eq!(result, Err("Mount path must start with '/'"));
    }

    fn assert_from_infallible<T: From<Infallible>>() {}

    #[test]
    fn test_from_infallible() {
        assert_from_infallible::<ApiError>();
    }

    #[test]
    fn test_host_set_and_get() {
        let mut app = App::new();
        assert_eq!(app.settings.host, "0.0.0.0");
        app.host("127.0.0.1");
        assert_eq!(app.settings.host, "127.0.0.1");
        app.host("::1");
        assert_eq!(app.settings.host, "::1");
        app.host("");
        assert_eq!(app.settings.host, "");
    }

    #[test]
    fn test_http2_config() {
        let mut app = App::new();
        assert_eq!(app.settings.http2_config, Http2Config::default());
        app.http2_config(Http2Config {
            http2_only: false,
            max_concurrent_streams: None,
            initial_stream_window_size: None,
            initial_connection_window_size: None,
            adaptive_window: None,
            max_frame_size: None,
            max_header_list_size: None,
            keep_alive_interval: None,
            keep_alive_timeout: None,
            ..Default::default()
        });
        assert_eq!(
            app.settings.http2_config,
            Http2Config {
                http2_only: false,
                max_concurrent_streams: None,
                initial_stream_window_size: None,
                initial_connection_window_size: None,
                adaptive_window: None,
                max_frame_size: None,
                max_header_list_size: None,
                keep_alive_interval: None,
                keep_alive_timeout: None,
                ..Default::default()
            }
        );
    }

    #[test]
    fn test_use_shield() {
        let mut app = App::new();
        let initial = app.middlewares.len();
        app.use_shield(None);

        assert_eq!(app.middlewares.len(), initial + 1);

        let middleware = &app.middlewares[0];

        assert_eq!(middleware.path, "/");
        assert_eq!(middleware.middleware_type, MiddlewareType::Post);
    }

    #[test]
    fn test_use_cors() {
        let mut app = App::new();
        let initial = app.middlewares.len();
        app.use_cors(None);

        assert_eq!(app.middlewares.len(), initial + 1);

        let middleware = &app.middlewares[0];

        assert_eq!(middleware.path, "/");
        assert_eq!(middleware.middleware_type, MiddlewareType::Pre);
    }

    #[test]
    #[cfg(feature = "logger")]
    fn test_use_logger() {
        let mut app = App::new();
        let initial = app.middlewares.len();
        app.use_logger(None);
        assert_eq!(app.middlewares.len(), initial + 1);

        let middleware = &app.middlewares[0];

        assert_eq!(middleware.path, "/");
        assert_eq!(middleware.middleware_type, MiddlewareType::Post);
    }

    #[tokio::test]
    async fn test_use_rate_middleware() {
        let mut app = App::new();
        let initial = app.middlewares.len();
        app.use_rate_limiter(None);
        assert_eq!(app.middlewares.len(), initial + 1);

        let middleware = &app.middlewares[0];

        assert_eq!(middleware.path, "/");
        assert_eq!(middleware.middleware_type, MiddlewareType::Pre);
    }

    #[test]
    fn test_multiple_use_middleware() {
        let mut app = App::new();
        let initial = app.middlewares.len();
        app.use_shield(None);
        app.use_cors(None);
        app.use_body_limit(None);
        assert_eq!(app.middlewares.len(), initial + 3);

        let middleware = &app.middlewares[0];

        assert_eq!(middleware.path, "/");
        assert_eq!(middleware.middleware_type, MiddlewareType::Post);
    }

    #[test]
    fn test_router() {
        let mut router = Router::new("/api");
        router.get("/", |_: HttpRequest, res| async move {
            res.ok().text("Hello, world!")
        });
        router.get("/api", |_: HttpRequest, res| async move {
            res.ok().text("Hello, world!")
        });

        let mut app = App::new();

        app.router(router);

        assert!(app.get_routes("/api", HttpMethods::GET).is_some());
        assert!(app.get_routes("/api/api", HttpMethods::GET).is_some());
    }
}
