//! Axum extractors for rok-validate: [`Valid<T>`], [`ValidatedForm<T>`], [`ValidatedQuery<T>`].
//!
//! Requires `features = ["axum"]`.

use axum::{
    body::Body,
    extract::{
        rejection::{JsonRejection, QueryRejection},
        FromRequest, FromRequestParts, Query, Request,
    },
    http::{request::Parts, StatusCode},
    response::{IntoResponse, Response},
    Json,
};

use crate::{errors::ValidationErrors, Validate};

// ── Valid<T> ──────────────────────────────────────────────────────────────────

/// Axum extractor that deserializes `T` from the JSON body and validates it.
///
/// If deserialization fails the request is rejected with the JSON error.
/// If validation fails a `422 Unprocessable Entity` is returned with the
/// structured error map.
///
/// # Example
///
/// ```rust,ignore
/// use rok_validate::{Valid, Validate};
/// use serde::Deserialize;
///
/// #[derive(Deserialize, Validate)]
/// pub struct LoginRequest {
///     #[validate(required, email)]
///     pub email: String,
///     #[validate(required, min = 8)]
///     pub password: String,
/// }
///
/// pub async fn login(Valid(body): Valid<LoginRequest>) -> impl IntoResponse {
///     // body is guaranteed valid here
/// }
/// ```
pub struct Valid<T>(pub T);

impl<T> Valid<T> {
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T, S> FromRequest<S> for Valid<T>
where
    T: serde::de::DeserializeOwned + Validate + Send + 'static,
    S: Send + Sync,
{
    type Rejection = ValidRejection;

    async fn from_request(req: Request<Body>, state: &S) -> Result<Self, Self::Rejection> {
        let Json(value) = Json::<T>::from_request(req, state)
            .await
            .map_err(ValidRejection::Json)?;

        value.validate().map_err(ValidRejection::Validation)?;

        Ok(Valid(value))
    }
}

// ── ValidRejection ────────────────────────────────────────────────────────────

/// Rejection type for [`Valid`]: either a JSON parse error or validation errors.
pub enum ValidRejection {
    Json(JsonRejection),
    Validation(ValidationErrors),
}

impl IntoResponse for ValidRejection {
    fn into_response(self) -> Response {
        match self {
            Self::Json(e) => e.into_response(),
            Self::Validation(errors) => {
                let body = serde_json::json!({
                    "message": "Validation failed",
                    "errors": errors,
                });
                (StatusCode::UNPROCESSABLE_ENTITY, Json(body)).into_response()
            }
        }
    }
}

// ── FormRequest ───────────────────────────────────────────────────────────────

/// Extension of [`Validate`] with an async authorization gate.
///
/// Implement this for request types that need both input validation AND
/// authorization checks before the handler runs.
///
/// # Example
///
/// ```rust,ignore
/// use rok_validate::{Validate, FormRequest, ValidatedForm};
///
/// #[derive(Deserialize, Validate)]
/// pub struct UpdatePostRequest {
///     #[validate(required, max = 200)]
///     pub title: String,
/// }
///
/// impl FormRequest for UpdatePostRequest {
///     async fn authorize(&self) -> bool {
///         // e.g. check current user owns the post
///         true
///     }
/// }
///
/// pub async fn update(ValidatedForm(body): ValidatedForm<UpdatePostRequest>) -> impl IntoResponse {
///     // validated + authorized
/// }
/// ```
pub trait FormRequest: Validate + Send {
    /// Return `true` if the request is authorized.  Called after validation.
    ///
    /// Returning `false` causes a `403 Forbidden` response.
    fn authorize(&self) -> impl std::future::Future<Output = bool> + Send {
        std::future::ready(true)
    }
}

// ── ValidatedForm<T> ─────────────────────────────────────────────────────────

/// Axum extractor that deserializes JSON body, validates, and calls `authorize()`.
///
/// Returns `422 Unprocessable Entity` on validation failure, `403 Forbidden` if
/// `authorize()` returns `false`.
pub struct ValidatedForm<T>(pub T);

impl<T> ValidatedForm<T> {
    pub fn into_inner(self) -> T {
        self.0
    }
}

/// Rejection type for [`ValidatedForm`].
pub enum FormRejection {
    Json(JsonRejection),
    Validation(ValidationErrors),
    Forbidden,
}

impl IntoResponse for FormRejection {
    fn into_response(self) -> Response {
        match self {
            Self::Json(e) => e.into_response(),
            Self::Validation(errors) => {
                let body = serde_json::json!({
                    "message": "Validation failed",
                    "errors": errors,
                });
                (StatusCode::UNPROCESSABLE_ENTITY, Json(body)).into_response()
            }
            Self::Forbidden => {
                let body = serde_json::json!({"message": "Forbidden"});
                (StatusCode::FORBIDDEN, Json(body)).into_response()
            }
        }
    }
}

impl<T, S> FromRequest<S> for ValidatedForm<T>
where
    T: serde::de::DeserializeOwned + FormRequest + 'static,
    S: Send + Sync,
{
    type Rejection = FormRejection;

    async fn from_request(req: Request<Body>, state: &S) -> Result<Self, Self::Rejection> {
        let Json(value) = Json::<T>::from_request(req, state)
            .await
            .map_err(FormRejection::Json)?;

        value.validate().map_err(FormRejection::Validation)?;

        if !value.authorize().await {
            return Err(FormRejection::Forbidden);
        }

        Ok(ValidatedForm(value))
    }
}

// ── ValidatedQuery<T> ─────────────────────────────────────────────────────────

/// Axum extractor that deserializes query parameters into `T` and validates them.
///
/// Returns `422 Unprocessable Entity` on validation failure.
///
/// # Example
///
/// ```rust,ignore
/// #[derive(Deserialize, Validate)]
/// pub struct SearchQuery {
///     #[validate(required, min = 2)]
///     pub q: String,
///     #[validate(optional)]
///     pub page: Option<u32>,
/// }
///
/// pub async fn search(ValidatedQuery(query): ValidatedQuery<SearchQuery>) -> impl IntoResponse {
///     // query.q is validated
/// }
/// ```
pub struct ValidatedQuery<T>(pub T);

impl<T> ValidatedQuery<T> {
    pub fn into_inner(self) -> T {
        self.0
    }
}

/// Rejection type for [`ValidatedQuery`].
pub enum QueryValidationRejection {
    Query(QueryRejection),
    Validation(ValidationErrors),
}

impl IntoResponse for QueryValidationRejection {
    fn into_response(self) -> Response {
        match self {
            Self::Query(e) => e.into_response(),
            Self::Validation(errors) => {
                let body = serde_json::json!({
                    "message": "Validation failed",
                    "errors": errors,
                });
                (StatusCode::UNPROCESSABLE_ENTITY, Json(body)).into_response()
            }
        }
    }
}

impl<T, S> FromRequestParts<S> for ValidatedQuery<T>
where
    T: serde::de::DeserializeOwned + Validate + Send + 'static,
    S: Send + Sync,
{
    type Rejection = QueryValidationRejection;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let Query(value) = Query::<T>::from_request_parts(parts, state)
            .await
            .map_err(QueryValidationRejection::Query)?;

        value
            .validate()
            .map_err(QueryValidationRejection::Validation)?;

        Ok(ValidatedQuery(value))
    }
}
