//! Error types for Soot applications.

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use thiserror::Error;

/// Application error type that integrates with Axum.
///
/// This error type automatically converts to HTTP responses with appropriate
/// status codes. Use it as the error type in your handlers.
///
/// # Example
///
/// ```ignore
/// use soot::prelude::*;
///
/// async fn get_user(Path(id): Path<i32>) -> Result<Html, AppError> {
///     let user = find_user(id).await.ok_or(AppError::NotFound)?;
///     Ok(views::users::show(user))
/// }
/// ```
#[derive(Error, Debug)]
pub enum AppError {
    /// Resource not found (404)
    #[error("Not found")]
    NotFound,

    /// Bad request (400)
    #[error("Bad request: {0}")]
    BadRequest(String),

    /// Unauthorized (401)
    #[error("Unauthorized")]
    Unauthorized,

    /// Forbidden (403)
    #[error("Forbidden")]
    Forbidden,

    /// Validation error (422)
    #[error("Validation error: {0}")]
    Validation(String),

    /// Internal server error (500)
    #[error("Internal error: {0}")]
    Internal(String),

    /// Database error
    #[cfg(feature = "postgres")]
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),

    #[cfg(all(feature = "sqlite", not(feature = "postgres")))]
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
}

impl AppError {
    /// Create a bad request error with a message.
    pub fn bad_request(msg: impl Into<String>) -> Self {
        Self::BadRequest(msg.into())
    }

    /// Create a validation error with a message.
    pub fn validation(msg: impl Into<String>) -> Self {
        Self::Validation(msg.into())
    }

    /// Create an internal error with a message.
    pub fn internal(msg: impl Into<String>) -> Self {
        Self::Internal(msg.into())
    }
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let status = match &self {
            Self::NotFound => StatusCode::NOT_FOUND,
            Self::BadRequest(_) => StatusCode::BAD_REQUEST,
            Self::Unauthorized => StatusCode::UNAUTHORIZED,
            Self::Forbidden => StatusCode::FORBIDDEN,
            Self::Validation(_) => StatusCode::UNPROCESSABLE_ENTITY,
            Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
            #[cfg(any(feature = "postgres", feature = "sqlite"))]
            Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
        };

        // In production, you might want to hide internal error details
        let body = self.to_string();

        (status, body).into_response()
    }
}

/// Result type alias using `AppError`.
pub type AppResult<T> = Result<T, AppError>;

/// Validation errors for form input.
#[derive(Debug, Default, Clone)]
pub struct ValidationErrors {
    errors: Vec<FieldError>,
}

/// A single field validation error.
#[derive(Debug, Clone)]
pub struct FieldError {
    pub field: String,
    pub message: String,
}

impl ValidationErrors {
    /// Create a new empty validation errors collection.
    #[must_use]
    pub fn new() -> Self {
        Self { errors: Vec::new() }
    }

    /// Add a field error.
    pub fn add(&mut self, field: impl Into<String>, message: impl Into<String>) {
        self.errors.push(FieldError {
            field: field.into(),
            message: message.into(),
        });
    }

    /// Check if there are any errors.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.errors.is_empty()
    }

    /// Get all errors.
    #[must_use]
    pub fn errors(&self) -> &[FieldError] {
        &self.errors
    }

    /// Get errors for a specific field.
    #[must_use]
    pub fn for_field(&self, field: &str) -> Vec<&FieldError> {
        self.errors.iter().filter(|e| e.field == field).collect()
    }

    /// Convert to an `AppError` if there are any errors.
    #[must_use]
    pub fn into_result<T>(self, value: T) -> Result<T, AppError> {
        if self.is_empty() {
            Ok(value)
        } else {
            let messages: Vec<_> = self.errors.iter().map(|e| format!("{}: {}", e.field, e.message)).collect();
            Err(AppError::Validation(messages.join(", ")))
        }
    }
}
