//! JWT authentication and verification logic.
//!
//! This module provides RS256 JWT token verification, claims extraction,
//! and token management utilities.

use base64::{engine::general_purpose, Engine as _};
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};

use crate::error::{AuthError, AuthErrorKind};

/// Default Control Center URL for authentication.
pub const DEFAULT_CONTROL_CENTER_URL: &str = "http://localhost:8081";

/// JWT Claims structure for provisioning platform tokens.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
    /// Subject (user ID)
    pub sub: String,
    /// Username
    pub username: String,
    /// User email
    pub email: String,
    /// User roles
    pub roles: Vec<String>,
    /// Expiration time (Unix timestamp)
    pub exp: i64,
    /// Issued at time (Unix timestamp)
    pub iat: i64,
    /// Not before time (Unix timestamp)
    #[serde(default)]
    pub nbf: i64,
    /// JWT ID (unique identifier)
    #[serde(default)]
    pub jti: String,
    /// Issuer
    #[serde(default)]
    pub iss: String,
    /// Audience
    #[serde(default)]
    pub aud: String,
}

/// Result of token verification.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
    /// Whether the token is valid
    pub valid: bool,
    /// Extracted claims if valid
    pub claims: Option<Claims>,
    /// Error message if invalid
    pub error: Option<String>,
    /// Time remaining until expiration (seconds)
    pub expires_in: Option<i64>,
}

impl VerificationResult {
    /// Creates a successful verification result.
    pub fn success(claims: Claims) -> Self {
        let now = chrono::Utc::now().timestamp();
        let expires_in = claims.exp - now;
        Self {
            valid: true,
            claims: Some(claims),
            error: None,
            expires_in: Some(expires_in),
        }
    }

    /// Creates a failed verification result.
    pub fn failure(error: impl Into<String>) -> Self {
        Self {
            valid: false,
            claims: None,
            error: Some(error.into()),
            expires_in: None,
        }
    }
}

/// Decodes and extracts claims from a JWT without verification.
///
/// This is useful for inspecting token contents before verification
/// or when verification is not possible (e.g., no public key available).
///
/// # Arguments
///
/// * `token` - The JWT token string
///
/// # Returns
///
/// Returns the decoded claims or an error if the token format is invalid.
pub fn decode_claims_unverified(token: &str) -> Result<Claims, AuthError> {
    let parts: Vec<&str> = token.split('.').collect();
    if parts.len() != 3 {
        return Err(AuthError::invalid_token("Token must have 3 parts separated by '.'"));
    }

    let payload = parts[1];
    let decoded = general_purpose::URL_SAFE_NO_PAD
        .decode(payload)
        .map_err(|e| AuthError::invalid_token(format!("Failed to decode payload: {}", e)))?;

    let claims: Claims = serde_json::from_slice(&decoded)
        .map_err(|e| AuthError::invalid_token(format!("Failed to parse claims: {}", e)))?;

    Ok(claims)
}

/// Verifies a JWT token using RS256 algorithm with the provided public key.
///
/// # Arguments
///
/// * `token` - The JWT token string
/// * `public_key_pem` - The RSA public key in PEM format
///
/// # Returns
///
/// Returns a VerificationResult indicating whether the token is valid
/// and containing the claims if verification succeeded.
pub fn verify_token_rs256(token: &str, public_key_pem: &str) -> Result<VerificationResult, AuthError> {
    // Verify the token header uses RS256
    let header = decode_header(token)
        .map_err(|e| AuthError::invalid_token(format!("Failed to decode header: {}", e)))?;

    if header.alg != Algorithm::RS256 {
        return Err(AuthError::new(
            AuthErrorKind::SignatureVerificationFailed,
            format!("Expected RS256 algorithm, got {:?}", header.alg),
        ));
    }

    // Create decoding key from PEM
    let decoding_key = DecodingKey::from_rsa_pem(public_key_pem.as_bytes())
        .map_err(|e| AuthError::configuration_error(format!("Invalid public key: {}", e)))?;

    // Set up validation
    let mut validation = Validation::new(Algorithm::RS256);
    validation.validate_exp = true;
    validation.validate_nbf = true;

    // Decode and verify
    match decode::<Claims>(token, &decoding_key, &validation) {
        Ok(token_data) => Ok(VerificationResult::success(token_data.claims)),
        Err(e) => {
            let error_msg = match e.kind() {
                jsonwebtoken::errors::ErrorKind::ExpiredSignature => "Token has expired",
                jsonwebtoken::errors::ErrorKind::InvalidSignature => "Invalid signature",
                jsonwebtoken::errors::ErrorKind::InvalidToken => "Invalid token format",
                jsonwebtoken::errors::ErrorKind::InvalidIssuer => "Invalid issuer",
                jsonwebtoken::errors::ErrorKind::InvalidAudience => "Invalid audience",
                _ => "Token verification failed",
            };
            Ok(VerificationResult::failure(error_msg))
        }
    }
}

/// Verifies a JWT token locally by checking format and expiration.
///
/// This performs basic validation without cryptographic verification:
/// - Token format (3 parts)
/// - Expiration time
/// - Not-before time
///
/// Use this for quick local checks when the public key is not available.
pub fn verify_token_local(token: &str) -> Result<VerificationResult, AuthError> {
    let claims = decode_claims_unverified(token)?;
    let now = chrono::Utc::now().timestamp();

    // Check expiration
    if claims.exp < now {
        return Ok(VerificationResult::failure(format!(
            "Token expired {} seconds ago",
            now - claims.exp
        )));
    }

    // Check not-before (if set)
    if claims.nbf > 0 && claims.nbf > now {
        return Ok(VerificationResult::failure(format!(
            "Token not valid for {} more seconds",
            claims.nbf - now
        )));
    }

    Ok(VerificationResult::success(claims))
}

/// Checks if a token is expired.
pub fn is_token_expired(token: &str) -> Result<bool, AuthError> {
    let claims = decode_claims_unverified(token)?;
    let now = chrono::Utc::now().timestamp();
    Ok(claims.exp < now)
}

/// Gets the time remaining until token expiration in seconds.
pub fn get_token_expiry_seconds(token: &str) -> Result<i64, AuthError> {
    let claims = decode_claims_unverified(token)?;
    let now = chrono::Utc::now().timestamp();
    Ok(claims.exp - now)
}

/// Extracts the user ID from a token.
pub fn get_user_id(token: &str) -> Result<String, AuthError> {
    let claims = decode_claims_unverified(token)?;
    Ok(claims.sub)
}

/// Extracts the username from a token.
pub fn get_username(token: &str) -> Result<String, AuthError> {
    let claims = decode_claims_unverified(token)?;
    Ok(claims.username)
}

/// Extracts the roles from a token.
pub fn get_roles(token: &str) -> Result<Vec<String>, AuthError> {
    let claims = decode_claims_unverified(token)?;
    Ok(claims.roles)
}

#[cfg(test)]
mod tests {
    use super::*;

    // Test token (expired, but valid format)
    const TEST_TOKEN: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsInVzZXJuYW1lIjoiYWRtaW4iLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sImV4cCI6MTcwMDAwMDAwMCwiaWF0IjoxNjk5OTk2NDAwLCJuYmYiOjAsImp0aSI6Imp0aS0xMjMiLCJpc3MiOiJwcm92aXNpb25pbmciLCJhdWQiOiJwcm92aXNpb25pbmctY2xpIn0.signature";

    #[test]
    fn test_decode_claims_unverified_invalid_format() {
        let result = decode_claims_unverified("not.a.valid.token.format");
        assert!(result.is_err());
    }

    #[test]
    fn test_decode_claims_unverified_missing_parts() {
        let result = decode_claims_unverified("only.two");
        assert!(result.is_err());
        let error = result.unwrap_err();
        assert_eq!(error.kind, AuthErrorKind::InvalidToken);
    }

    #[test]
    fn test_verification_result_success() {
        let claims = Claims {
            sub: "user-123".to_string(),
            username: "admin".to_string(),
            email: "admin@example.com".to_string(),
            roles: vec!["admin".to_string()],
            exp: chrono::Utc::now().timestamp() + 3600,
            iat: chrono::Utc::now().timestamp(),
            nbf: 0,
            jti: "jti-123".to_string(),
            iss: "provisioning".to_string(),
            aud: "cli".to_string(),
        };
        let result = VerificationResult::success(claims);
        assert!(result.valid);
        assert!(result.claims.is_some());
        assert!(result.expires_in.is_some());
    }

    #[test]
    fn test_verification_result_failure() {
        let result = VerificationResult::failure("test error");
        assert!(!result.valid);
        assert!(result.claims.is_none());
        assert_eq!(result.error, Some("test error".to_string()));
    }

    #[test]
    fn test_is_token_expired_handles_invalid() {
        let result = is_token_expired("invalid");
        assert!(result.is_err());
    }
}
