use std::sync::Arc;

use axum::{
    body::Body,
    extract::State,
    http::{header, HeaderMap, HeaderValue, StatusCode},
    response::IntoResponse,
};
use tokio::fs;

use crate::{auth::require_bootstrap_auth, state::AppState};

/// GET /api/v1/bootstrap/flake-bundle
pub async fn flake_bundle(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    require_bootstrap_auth(&state, &headers)?;

    let Some(path) = state.config.bootstrap_flake_bundle_path.as_ref() else {
        return Err((
            StatusCode::SERVICE_UNAVAILABLE,
            "bootstrap flake bundle not configured".to_string(),
        ));
    };

    let bytes = fs::read(path).await.map_err(|error| {
        let status = if error.kind() == std::io::ErrorKind::NotFound {
            StatusCode::NOT_FOUND
        } else {
            StatusCode::INTERNAL_SERVER_ERROR
        };
        (
            status,
            format!(
                "failed to read bootstrap flake bundle {}: {}",
                path.display(),
                error
            ),
        )
    })?;

    let headers = [
        (
            header::CONTENT_TYPE,
            HeaderValue::from_static("application/gzip"),
        ),
        (
            header::CONTENT_DISPOSITION,
            HeaderValue::from_static("attachment; filename=\"ultracloud-flake-bundle.tar.gz\""),
        ),
    ];

    Ok((headers, Body::from(bytes)))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{build_router, config::Config};
    use axum::{body::to_bytes, http::Request};
    use std::{
        fs,
        time::{SystemTime, UNIX_EPOCH},
    };
    use tower::ServiceExt;

    fn temp_path(name: &str) -> std::path::PathBuf {
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        std::env::temp_dir().join(format!("{}-{}-{}", name, std::process::id(), nanos))
    }

    #[tokio::test]
    async fn flake_bundle_route_serves_configured_bundle() {
        let bundle_path = temp_path("deployer-flake-bundle");
        fs::write(&bundle_path, b"bundle-bytes").unwrap();

        let mut config = Config::default();
        config.bootstrap_token = Some("test-token".to_string());
        config.bootstrap_flake_bundle_path = Some(bundle_path.clone());
        let state = Arc::new(AppState::with_config(config));
        let app = build_router(state);

        let response = app
            .oneshot(
                Request::builder()
                    .uri("/api/v1/bootstrap/flake-bundle")
                    .header("x-deployer-token", "test-token")
                    .body(Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
            response
                .headers()
                .get(header::CONTENT_TYPE)
                .and_then(|value| value.to_str().ok()),
            Some("application/gzip")
        );

        let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
        assert_eq!(body.as_ref(), b"bundle-bytes");

        let _ = fs::remove_file(bundle_path);
    }

    #[tokio::test]
    async fn flake_bundle_route_requires_configured_bundle() {
        let mut config = Config::default();
        config.bootstrap_token = Some("test-token".to_string());
        let state = Arc::new(AppState::with_config(config));
        let app = build_router(state);

        let response = app
            .oneshot(
                Request::builder()
                    .uri("/api/v1/bootstrap/flake-bundle")
                    .header("x-deployer-token", "test-token")
                    .body(Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
    }
}
