//! JWT token handling

use anyhow::{anyhow, Result};
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// JWT claims
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
    /// Subject (user ID)
    pub sub: String,
    /// Username
    pub username: String,
    /// Role (admin, operator, viewer)
    pub role: String,
    /// Permissions list
    pub permissions: Vec<String>,
    /// Expiration time (unix timestamp)
    pub exp: i64,
    /// Issued at (unix timestamp)
    pub iat: i64,
}

impl Claims {
    /// Check if user has a specific permission
    pub fn has_permission(&self, permission: &str) -> bool {
        // Admins have all permissions
        if self.role == "admin" {
            return true;
        }
        self.permissions.contains(&permission.to_string())
    }

    /// Check if user is admin
    pub fn is_admin(&self) -> bool {
        self.role == "admin"
    }

    /// Get user ID as UUID
    pub fn user_id(&self) -> Result<Uuid> {
        Uuid::parse_str(&self.sub).map_err(|e| anyhow!("Invalid user ID in token: {}", e))
    }
}

/// JWT configuration
#[derive(Clone)]
pub struct JwtConfig {
    secret: String,
    expiry_hours: i64,
}

impl JwtConfig {
    /// Create new JWT config
    pub fn new(secret: String, expiry_hours: i64) -> Self {
        Self { secret, expiry_hours }
    }

    /// Create a JWT token for a user
    pub fn create_token(
        &self,
        user_id: Uuid,
        username: &str,
        role: &str,
        permissions: Vec<String>,
    ) -> Result<String> {
        let now = Utc::now();
        let exp = now + Duration::hours(self.expiry_hours);

        let claims = Claims {
            sub: user_id.to_string(),
            username: username.to_string(),
            role: role.to_string(),
            permissions,
            exp: exp.timestamp(),
            iat: now.timestamp(),
        };

        let token = encode(
            &Header::default(),
            &claims,
            &EncodingKey::from_secret(self.secret.as_bytes()),
        )
        .map_err(|e| anyhow!("Failed to create token: {}", e))?;

        Ok(token)
    }

    /// Validate and decode a JWT token
    ///
    /// SEC-13: Explicitly enforces token expiration
    /// - Validates signature against secret
    /// - Checks exp claim (expiration time)
    /// - Checks iat claim (issued at time)
    /// - Rejects expired tokens
    pub fn validate_token(&self, token: &str) -> Result<Claims> {
        // SEC-13: Explicit validation configuration
        let mut validation = Validation::default();
        validation.validate_exp = true;  // Enforce expiration check
        validation.validate_nbf = false; // Not using "not before" claim
        validation.leeway = 0;           // No clock skew tolerance

        let token_data = decode::<Claims>(
            token,
            &DecodingKey::from_secret(self.secret.as_bytes()),
            &validation,
        )
        .map_err(|e| anyhow!("Invalid token: {}", e))?;

        // Additional check: Ensure token hasn't expired (redundant but explicit)
        let now = Utc::now().timestamp();
        if token_data.claims.exp < now {
            return Err(anyhow!("Token has expired"));
        }

        Ok(token_data.claims)
    }
}

// Removed insecure default_jwt_secret() function - JWT_SECRET must be set via environment variable

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

    #[test]
    fn test_create_and_validate_token() {
        let config = JwtConfig::new("test-secret".to_string(), 24);
        let user_id = Uuid::new_v4();

        let token = config.create_token(
            user_id,
            "testuser",
            "admin",
            vec!["view".to_string(), "control".to_string()],
        ).unwrap();

        let claims = config.validate_token(&token).unwrap();
        assert_eq!(claims.username, "testuser");
        assert_eq!(claims.role, "admin");
        assert!(claims.has_permission("view"));
        assert!(claims.has_permission("manage_users")); // admin has all
    }

    #[test]
    fn test_invalid_token() {
        let config = JwtConfig::new("test-secret".to_string(), 24);
        assert!(config.validate_token("invalid.token.here").is_err());
    }
}
