use axum::http::Method;
use jsonwebtoken::{DecodingKey, TokenData, Validation, decode};
use log::debug;
use serde::Deserialize;

/// JWT claims for permission-based access control.
///
/// External token generators should include these claims in the JWT payload.
/// The server validates the signature and checks permissions for each request.
///
/// # Example token payload
///
/// ```json
/// {
///   "sub": "my-client",
///   "exp": 1735689600,
///   "read": true,
///   "write": true,
///   "delete": false
/// }
/// ```
#[derive(Debug, Deserialize)]
pub struct Claims {
    /// Subject (client identifier).
    pub sub: String,
    /// Expiration time (Unix timestamp).
    pub exp: usize,
    /// Read permission (GET requests).
    #[serde(default)]
    pub read: bool,
    /// Write permission (POST/PUT requests).
    #[serde(default)]
    pub write: bool,
    /// Delete permission (DELETE requests).
    #[serde(default)]
    pub delete: bool,
}

/// Returns the required permission for an HTTP method.
///
/// # Mapping
///
/// - GET, HEAD → "read"
/// - POST, PUT, PATCH → "write"
/// - DELETE → "delete"
///
/// # Arguments
///
/// * `method` - The HTTP method of the incoming request.
///
/// # Returns
///
/// A string slice representing the required permission.
pub fn required_permission(method: &Method) -> &'static str {
    if method == Method::GET || method == Method::HEAD {
        "read"
    } else if method == Method::DELETE {
        "delete"
    } else {
        "write"
    }
}

/// Checks if the JWT claims grant the required permission.
///
/// # Arguments
///
/// * `claims` - The validated JWT claims.
/// * `permission` - The required permission string ("read", "write", or "delete").
///
/// # Returns
///
/// `true` if the claims grant the permission, `false` otherwise.
pub fn check_permission(claims: &Claims, permission: &str) -> bool {
    match permission {
        "read" => claims.read,
        "write" => claims.write,
        "delete" => claims.delete,
        _ => false,
    }
}

/// Validates a JWT token and returns the claims.
///
/// Uses HMAC-SHA256 signature verification with the provided secret.
///
/// # Arguments
///
/// * `token` - The JWT token string (without "Bearer " prefix).
/// * `secret` - The secret key used to verify the signature.
///
/// # Returns
///
/// * `Ok(Claims)` - The validated claims if the token is valid.
/// * `Err(String)` - A human-readable error message if validation fails.
pub fn validate_jwt(token: &str, secret: &str) -> Result<Claims, String> {
    let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256);
    validation.algorithms = vec![jsonwebtoken::Algorithm::HS256];
    validation.set_required_spec_claims(&["exp", "sub"]);

    let token_data: TokenData<Claims> = decode::<Claims>(
        token,
        &DecodingKey::from_secret(secret.as_bytes()),
        &validation,
    )
    .map_err(|e| {
        debug!("JWT validation failed: {e}");
        match e.kind() {
            jsonwebtoken::errors::ErrorKind::ExpiredSignature => "Token expired".to_string(),
            jsonwebtoken::errors::ErrorKind::InvalidSignature => "Invalid token".to_string(),
            jsonwebtoken::errors::ErrorKind::InvalidToken => "Malformed token".to_string(),
            jsonwebtoken::errors::ErrorKind::ImmatureSignature => "Token not yet valid".to_string(),
            _ => "Invalid token".to_string(),
        }
    })?;

    Ok(token_data.claims)
}
