use poem::{
    Response, handler,
    http::{StatusCode, header},
    web::Path,
};
use rust_embed::RustEmbed;

#[derive(RustEmbed)]
#[folder = "../frontend/dist"]
struct EmbeddedAssets;

fn serve_embedded(path: &str) -> Response {
    let normalized = if path.is_empty() {
        "index.html"
    } else {
        path.trim_start_matches('/')
    };

    let is_asset_request = normalized.starts_with("assets/");
    let asset = if is_asset_request {
        EmbeddedAssets::get(normalized)
    } else {
        EmbeddedAssets::get(normalized).or_else(|| {
            if normalized == "index.html" {
                None
            } else {
                EmbeddedAssets::get("index.html")
            }
        })
    };

    match asset {
        Some(content) => {
            let body = content.data.into_owned();
            let mime = mime_guess::from_path(normalized)
                .first_or_octet_stream()
                .to_string();

            Response::builder()
                .status(StatusCode::OK)
                .header(header::CONTENT_TYPE, mime)
                .body(body)
        }
        None => Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body("Not Found"),
    }
}

/// Serve a single embedded asset from the `assets/` folder.
#[handler]
pub fn embedded_asset(Path(path): Path<String>) -> Response {
    let asset_path = format!("assets/{path}");
    serve_embedded(&asset_path)
}

/// Serve an embedded file by path (falls back to `index.html` for SPA routing).
#[handler]
pub fn embedded_file(Path(path): Path<String>) -> Response {
    serve_embedded(&path)
}

/// Serve the embedded SPA entrypoint.
#[handler]
pub fn embedded_index() -> Response {
    serve_embedded("index.html")
}

#[cfg(test)]
mod tests {
    use super::*;
    use poem::http::StatusCode;

    #[test]
    fn non_asset_path_spa_fallback_or_not_found() {
        // Non-asset paths fall back to index.html for SPA client-side routing.
        // In release builds (with embedded dist/) this returns 200.
        // In debug builds without a built frontend dist/ it returns 404.
        let response = serve_embedded("__nonexistent_spa_route__.html");
        let status = response.status();
        assert!(
            status == StatusCode::OK || status == StatusCode::NOT_FOUND,
            "unexpected status: {status}",
        );
    }

    #[test]
    fn missing_asset_path_prefix_returns_not_found() {
        // assets/ prefix: no SPA fallback – returns 404 if the file does not exist
        let response = serve_embedded("assets/__nonexistent__.js");
        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }

    #[test]
    fn serve_embedded_does_not_panic_on_empty_path() {
        // Empty path normalises to index.html; OK in release, 404 in debug without dist/
        let response = serve_embedded("");
        let status = response.status();
        assert!(
            status == StatusCode::OK || status == StatusCode::NOT_FOUND,
            "unexpected status: {status}",
        );
    }

    #[test]
    fn embedded_assets_struct_is_iterable() {
        // Verifies that rust-embed compiled the EmbeddedAssets struct correctly.
        // In debug builds without a built frontend dist/ directory the iterator is empty; that is
        // expected.  In release builds it will contain all bundled frontend files.
        let _files: Vec<_> = EmbeddedAssets::iter().collect();
        // No assertion needed – the test passes as long as it compiles and does not panic.
    }

    #[tokio::test]
    async fn embedded_index_handler_returns_ok_or_not_found() {
        // Route the handler through TestClient; index.html is the SPA entry point.
        let app = poem::Route::new().at("/", poem::get(embedded_index));
        let cli = poem::test::TestClient::new(app);
        let resp = cli.get("/").send().await;
        let status = resp.0.status();
        assert!(
            status == StatusCode::OK || status == StatusCode::NOT_FOUND,
            "unexpected status: {status}",
        );
    }

    #[tokio::test]
    async fn embedded_file_handler_with_path_returns_ok_or_not_found() {
        // Non-asset paths fall back to index.html (SPA routing) or 404.
        let app = poem::Route::new().at("/*path", poem::get(embedded_file));
        let cli = poem::test::TestClient::new(app);
        let resp = cli.get("/__spa_route__").send().await;
        let status = resp.0.status();
        assert!(
            status == StatusCode::OK || status == StatusCode::NOT_FOUND,
            "unexpected status: {status}",
        );
    }

    #[tokio::test]
    async fn embedded_asset_handler_missing_file_returns_not_found() {
        // The assets/ prefix disables SPA fallback; missing files must return 404.
        let app = poem::Route::new().at("/assets/*path", poem::get(embedded_asset));
        let cli = poem::test::TestClient::new(app);
        let resp = cli.get("/assets/__nonexistent__.js").send().await;
        assert_eq!(resp.0.status(), StatusCode::NOT_FOUND);
    }
}
