//! GraphQL route handlers

use axum::{
    body::Body,
    extract::State,
    http::{header::CONTENT_TYPE, Request, StatusCode},
    response::IntoResponse,
};
use async_graphql::{futures_util::TryStreamExt, http::MultipartOptions};
use async_graphql_axum::GraphQLResponse;
use tokio_util::compat::TokioAsyncReadCompatExt;

use crate::auth::{OptionalUser, ProfileType, RequiredUser};
use crate::db::Database;
use crate::graphql::NexusSchema;
use crate::models::TeamRole;

/// GraphQL handler - supports both JSON and multipart requests (for file uploads)
pub async fn graphql_handler(
    State(schema): State<NexusSchema>,
    user: OptionalUser,
    req: Request<Body>,
) -> GraphQLResponse {
    // Get content type for parsing
    let content_type = req
        .headers()
        .get(CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());

    // Convert axum body stream to AsyncRead (futures-compatible)
    let body_stream = req
        .into_body()
        .into_data_stream()
        .map_err(|e| std::io::Error::other(e.to_string()));
    let body_reader = tokio_util::io::StreamReader::new(body_stream).compat();

    // Configure multipart options (50MB max file, 10 files max)
    let opts = MultipartOptions::default()
        .max_file_size(50 * 1024 * 1024)
        .max_num_files(10);

    // Parse request (handles both JSON and multipart automatically)
    let mut request = match async_graphql::http::receive_body(content_type, body_reader, opts).await
    {
        Ok(req) => req,
        Err(e) => {
            return async_graphql::Response::from_errors(vec![async_graphql::ServerError::new(
                format!("Failed to parse GraphQL request: {}", e),
                None,
            )])
            .into();
        }
    };

    // Add user context to GraphQL context if present
    if let Some(ctx) = user.0 {
        request = request.data(ctx);
    }

    schema.execute(request).await.into()
}

/// GraphQL Playground handler - admin only
pub async fn graphql_playground(
    State(db): State<Database>,
    user: RequiredUser,
) -> Result<impl IntoResponse, StatusCode> {
    // Must be a team profile
    if user.0.profile_type != ProfileType::Team {
        tracing::warn!(
            user_id = %user.0.user_id,
            "Non-team user attempted to access GraphQL playground"
        );
        return Err(StatusCode::FORBIDDEN);
    }

    // Check if user is admin
    let pool = db.pool().await;
    let role: Option<TeamRole> = sqlx::query_scalar(
        "SELECT role FROM team_profiles WHERE id = $1",
    )
    .bind(user.0.user_id)
    .fetch_optional(&*pool)
    .await
    .map_err(|e| {
        tracing::error!(error = %e, "Failed to fetch team profile role");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    match role {
        Some(TeamRole::Admin) => {
            // Serve the playground
            Ok(axum::response::Html(async_graphql::http::playground_source(
                async_graphql::http::GraphQLPlaygroundConfig::new("/v2/graphql")
                    .with_setting("request.credentials", "include"),
            )))
        }
        Some(_) => {
            tracing::warn!(
                user_id = %user.0.user_id,
                "Non-admin user attempted to access GraphQL playground"
            );
            Err(StatusCode::FORBIDDEN)
        }
        None => {
            tracing::warn!(
                user_id = %user.0.user_id,
                "Team profile not found for playground access"
            );
            Err(StatusCode::FORBIDDEN)
        }
    }
}
