//! JWT Service - Token validation and verification
//!
//! Migrated from trading_service to api for centralized authentication.
//! This service handles:
//! - JWT token validation (signature, expiry, claims)
//! - Token revocation checking via Redis
//! - Enhanced security validation (entropy, length, patterns)

use anyhow::{Context, Result};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use secrecy::ExposeSecret;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tracing::{error, info};

use super::revocation::{Jti, JwtRevocationService};

/// JWT claims structure
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct JwtClaims {
    /// JWT ID for revocation tracking (SECURITY: MANDATORY for revocation)
    pub jti: String,
    /// Subject (user ID)
    pub sub: String,
    /// Issued at timestamp
    pub iat: u64,
    /// Expiration timestamp
    pub exp: u64,
    /// Issuer
    pub iss: String,
    /// Audience
    pub aud: String,
    /// User roles
    pub roles: Vec<String>,
    /// Additional permissions
    pub permissions: Vec<String>,
    /// Token type: "access" or "refresh" (optional for backward compatibility)
    #[serde(default = "default_token_type")]
    pub token_type: String,
    /// Session ID for tracking related tokens (optional for backward compatibility)
    #[serde(default)]
    pub session_id: Option<String>,
}

fn default_token_type() -> String {
    "access".to_string()
}

impl JwtClaims {
    /// Convert to EnhancedJwtClaims for revocation tracking
    pub fn to_enhanced(&self) -> super::revocation::EnhancedJwtClaims {
        super::revocation::EnhancedJwtClaims {
            jti: self.jti.clone(),
            sub: self.sub.clone(),
            iat: self.iat,
            exp: self.exp,
            nbf: self.iat, // Use iat as nbf if not present
            iss: self.iss.clone(),
            aud: self.aud.clone(),
            roles: self.roles.clone(),
            permissions: self.permissions.clone(),
            token_type: self.token_type.clone(),
            session_id: self
                .session_id
                .clone()
                .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
        }
    }
}

/// JWT Service configuration
#[derive(Debug, Clone)]
pub struct JwtConfig {
    /// JWT secret for token verification
    pub jwt_secret: String,
    /// JWT issuer
    pub jwt_issuer: String,
    /// JWT audience
    pub jwt_audience: String,
}

impl JwtConfig {
    /// Create new JwtConfig with proper error handling
    ///
    /// Priority:
    /// 1. Vault (production) - secret/foxhunt/jwt
    /// 2. JWT_SECRET_FILE (file-based secret)
    /// 3. JWT_SECRET env var (development fallback)
    ///
    /// Returns error if JWT secret is not configured or invalid
    pub async fn new() -> Result<Self> {
        // Try loading from Vault first (production)
        if let Ok(vault_config) = Self::load_from_vault().await {
            info!("✅ JWT configuration loaded from Vault");
            return Ok(vault_config);
        }

        // Fallback to legacy file/env loading
        error!("Vault unavailable - using legacy JWT_SECRET_FILE/JWT_SECRET (development only)");

        let jwt_secret = Self::load_jwt_secret()
            .map_err(|e| anyhow::anyhow!("Failed to load JWT secret: {}", e))?;

        Ok(Self {
            jwt_secret,
            jwt_issuer: "foxhunt-api".to_string(),
            jwt_audience: "foxhunt-services".to_string(),
        })
    }

    /// Load JWT configuration from HashiCorp Vault (production)
    async fn load_from_vault() -> Result<Self> {
        use config::JwtConfig as VaultJwtConfig;

        let vault_config = VaultJwtConfig::load().await?;

        Ok(Self {
            jwt_secret: vault_config.secret().expose_secret().to_string(),
            jwt_issuer: vault_config.issuer().to_string(),
            jwt_audience: vault_config.audience().to_string(),
        })
    }

    /// Securely load JWT secret from file or environment with enhanced validation
    ///
    /// Priority: 1) JWT_SECRET_FILE path, 2) JWT_SECRET env var
    ///
    /// SECURITY: Enforces minimum 64-character (512-bit) secrets with entropy validation
    fn load_jwt_secret() -> Result<String> {
        // Try loading from secure file (recommended for production)
        if let Ok(secret_file_path) = std::env::var("JWT_SECRET_FILE") {
            match std::fs::read_to_string(&secret_file_path) {
                Ok(secret) => {
                    let trimmed_secret = secret.trim().to_string();
                    if let Err(e) = Self::validate_jwt_secret(&trimmed_secret) {
                        error!(
                            "JWT secret in file {} failed validation: {}",
                            secret_file_path, e
                        );
                    } else {
                        tracing::info!("JWT secret loaded from secure file: {}", secret_file_path);
                        return Ok(trimmed_secret);
                    }
                },
                Err(e) => {
                    error!("Failed to read JWT secret file {}: {}", secret_file_path, e);
                },
            }
        }

        // Fallback to environment variable (less secure, warn user)
        if let Ok(secret) = std::env::var("JWT_SECRET") {
            // WAVE 196: Relaxed validation for development secrets
            // Production should use JWT_SECRET_FILE with proper validation
            error!(
                "JWT secret loaded from environment variable - use JWT_SECRET_FILE for production"
            );
            // WAVE 149 Agent 411: Trim whitespace to match file loading behavior (line 103)
            return Ok(secret.trim().to_string());
        }

        Err(anyhow::anyhow!(
            "JWT secret not found or invalid. Requirements:\n\
             - Minimum 64 characters (512-bit security)\n\
             - High entropy (mixed case, numbers, symbols)\n\
             - No dictionary words or patterns\n\
             Production setup: JWT_SECRET_FILE=/opt/foxhunt/secrets/jwt_secret\n\
             Generate with: openssl rand -base64 64"
        ))
    }

    /// Validate JWT secret strength and entropy
    ///
    /// SECURITY: Enforces enterprise-grade JWT secret requirements
    fn validate_jwt_secret(secret: &str) -> Result<()> {
        // Length validation - minimum 64 characters (512 bits)
        if secret.len() < 64 {
            return Err(anyhow::anyhow!(
                "JWT secret too short: {} characters (minimum 64 required for 512-bit security)",
                secret.len()
            ));
        }

        // Maximum length check (prevent DoS)
        if secret.len() > 1024 {
            return Err(anyhow::anyhow!(
                "JWT secret too long: {} characters (maximum 1024 for performance)",
                secret.len()
            ));
        }

        // Character set validation - require mixed case, numbers, and symbols
        let has_lowercase = secret.chars().any(|c| c.is_ascii_lowercase());
        let has_uppercase = secret.chars().any(|c| c.is_ascii_uppercase());
        let has_digit = secret.chars().any(|c| c.is_ascii_digit());
        let has_symbol = secret.chars().any(|c| !c.is_alphanumeric());

        if !has_lowercase {
            return Err(anyhow::anyhow!("JWT secret must contain lowercase letters"));
        }
        if !has_uppercase {
            return Err(anyhow::anyhow!("JWT secret must contain uppercase letters"));
        }
        if !has_digit {
            return Err(anyhow::anyhow!("JWT secret must contain digits"));
        }
        if !has_symbol {
            return Err(anyhow::anyhow!("JWT secret must contain symbols"));
        }

        // Entropy estimation - check for repeated patterns
        if Self::has_weak_patterns(secret) {
            return Err(anyhow::anyhow!(
                "JWT secret contains weak patterns (repeated sequences, dictionary words)"
            ));
        }

        // Basic entropy check - should have reasonable character distribution
        let entropy_score = Self::calculate_entropy(secret);
        if entropy_score < 4.0 {
            return Err(anyhow::anyhow!(
                "JWT secret has low entropy: {:.2} bits/char (minimum 4.0 required)",
                entropy_score
            ));
        }

        Ok(())
    }

    /// Check for weak patterns in JWT secret
    fn has_weak_patterns(secret: &str) -> bool {
        // Check for repeated characters (more than 3 in a row)
        let mut prev_char = '\0';
        let mut repeat_count = 1;
        for c in secret.chars() {
            if c == prev_char {
                repeat_count += 1;
                if repeat_count > 3 {
                    return true;
                }
            } else {
                repeat_count = 1;
                prev_char = c;
            }
        }

        // Check for simple sequential patterns
        let bytes = secret.as_bytes();
        for window in bytes.windows(4) {
            // windows(4) guarantees 4 elements, use pattern matching for safety
            if let [a, b, c, d] = window {
                let ascending = a
                    .checked_add(1)
                    .map(|a_plus_1| a_plus_1 == *b)
                    .unwrap_or(false)
                    && b.checked_add(1)
                        .map(|b_plus_1| b_plus_1 == *c)
                        .unwrap_or(false)
                    && c.checked_add(1)
                        .map(|c_plus_1| c_plus_1 == *d)
                        .unwrap_or(false);

                // For descending, wrapping_sub is actually correct here as we're checking sequences
                let descending =
                    a.wrapping_sub(1) == *b && b.wrapping_sub(1) == *c && c.wrapping_sub(1) == *d;
                if ascending || descending {
                    return true;
                }
            }
        }

        // Check for common weak patterns
        let weak_patterns = [
            "1234", "abcd", "password", "secret", "admin", "user", "test", "demo", "qwer", "asdf",
            "0000", "1111", "aaaa", "bbbb", "cccc", "dddd", "eeee", "ffff",
        ];

        for pattern in &weak_patterns {
            if secret.to_lowercase().contains(pattern) {
                return true;
            }
        }

        false
    }

    /// Calculate Shannon entropy of the secret
    fn calculate_entropy(secret: &str) -> f64 {
        use std::collections::HashMap;

        let mut char_counts = HashMap::new();
        let total_chars = secret.len() as f64;

        // Count character frequencies
        for c in secret.chars() {
            *char_counts.entry(c).or_insert(0) += 1;
        }

        // Calculate Shannon entropy
        let mut entropy = 0.0;
        for count in char_counts.values() {
            let probability = *count as f64 / total_chars;
            // f64 arithmetic is checked at runtime for infinity/NaN
            let entropy_contribution = probability * probability.log2();
            if entropy_contribution.is_finite() {
                entropy -= entropy_contribution;
            }
        }

        entropy
    }
}

/// JWT token validator
pub struct JwtService {
    config: Arc<JwtConfig>,
    revocation_service: Option<Arc<JwtRevocationService>>,
}

impl JwtService {
    /// Create new JWT service
    pub fn new(config: JwtConfig) -> Self {
        Self {
            config: Arc::new(config),
            revocation_service: None,
        }
    }

    /// Set revocation service (call after initialization)
    pub fn set_revocation_service(&mut self, service: Arc<JwtRevocationService>) {
        self.revocation_service = Some(service);
    }

    /// Validate JWT token
    pub async fn validate_token(&self, token: &str) -> Result<JwtClaims> {
        // SECURITY: Enhanced JWT validation with stronger checks
        if token.is_empty() {
            return Err(anyhow::anyhow!("JWT token is empty"));
        }

        if token.len() > 8192 {
            return Err(anyhow::anyhow!("JWT token too long - possible attack"));
        }

        let key = DecodingKey::from_secret(self.config.jwt_secret.as_ref());
        let mut validation = Validation::new(Algorithm::HS256);

        // SECURITY: Strict validation settings with clock tolerance
        validation.set_issuer(&[&self.config.jwt_issuer]);
        validation.set_audience(&[&self.config.jwt_audience]);
        validation.validate_exp = true;
        validation.validate_nbf = false; // Disable NBF validation (optional claim)
        validation.leeway = 10; // 10 second tolerance for clock skew
        validation.validate_aud = true;

        let token_data = decode::<JwtClaims>(token, &key, &validation).map_err(|e| {
            error!("JWT decode failed: {:?}", e);
            error!(
                "Token (first 50 chars): {}...",
                &token[..50.min(token.len())]
            );
            error!(
                "Expected issuer: {}, audience: {}",
                self.config.jwt_issuer, self.config.jwt_audience
            );
            error!(
                "Validation settings: exp={}, nbf={}, aud={}, leeway={}",
                validation.validate_exp,
                validation.validate_nbf,
                validation.validate_aud,
                validation.leeway
            );
            anyhow::anyhow!("Invalid JWT token: {}", e)
        })?;

        // SECURITY: Check token revocation BEFORE other validations
        if let Some(revocation_service) = &self.revocation_service {
            let jti = Jti::from_string(token_data.claims.jti.clone());

            let is_revoked = revocation_service
                .is_revoked(&jti)
                .await
                .context("Failed to check token revocation status")?;

            if is_revoked {
                // Get revocation metadata for detailed error message
                if let Ok(Some(metadata)) = revocation_service.get_revocation_metadata(&jti).await {
                    error!(
                        "Revoked token attempted: jti={} user={} reason={} revoked_by={}",
                        jti,
                        metadata.user_id(),
                        metadata.reason(),
                        metadata.revoked_by()
                    );
                }
                return Err(anyhow::anyhow!("JWT token has been revoked"));
            }
        }

        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| anyhow::anyhow!("System time error: {}", e))?
            .as_secs();

        // SECURITY: Additional expiration check with buffer
        if token_data.claims.exp <= now {
            return Err(anyhow::anyhow!("JWT token expired"));
        }

        // SECURITY: Check token age (max 1 hour)
        let token_age = now
            .checked_sub(token_data.claims.iat)
            .ok_or_else(|| anyhow::anyhow!("Invalid token timestamp (iat in future)"))?;
        if token_age > 3600 {
            return Err(anyhow::anyhow!("JWT token too old"));
        }

        // SECURITY: Validate claims structure
        if token_data.claims.sub.is_empty() {
            return Err(anyhow::anyhow!("JWT subject claim is empty"));
        }

        // SECURITY: Validate JTI is present (required for revocation)
        if token_data.claims.jti.is_empty() {
            return Err(anyhow::anyhow!(
                "JWT must contain jti claim for revocation support"
            ));
        }

        if token_data.claims.roles.is_empty() {
            return Err(anyhow::anyhow!("JWT must contain at least one role"));
        }

        Ok(token_data.claims)
    }
}

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

    #[tokio::test]
    async fn test_jwt_config_new_with_valid_secret() {
        // WAVE G23: Test isolation - save/restore env state to avoid race conditions
        let original_jwt_secret = std::env::var("JWT_SECRET").ok();

        // Set a high-entropy test JWT secret
        std::env::set_var(
            "JWT_SECRET",
            "Kx7mP@9nR!2sW#5vY$8bC&3fG*6jH^1kL%4pQ+7tZ-0uN~9dM=5eV(8xS)2wT!6yA#4zB",
        );

        let config = JwtConfig::new()
            .await
            .expect("Should create config with valid JWT_SECRET");

        assert_eq!(config.jwt_issuer, "foxhunt-api");
        assert_eq!(config.jwt_audience, "foxhunt-services");
        assert!(config.jwt_secret.len() >= 64);

        // WAVE G23: Restore original env state
        match original_jwt_secret {
            Some(val) => std::env::set_var("JWT_SECRET", val),
            None => std::env::remove_var("JWT_SECRET"),
        }
    }

    #[tokio::test]
    async fn test_jwt_config_new_priority_vault_over_env() {
        // WAVE G23: Test isolation - save/restore env state to avoid race conditions
        let original_jwt_secret = std::env::var("JWT_SECRET").ok();

        // Test that Vault takes precedence over env vars (production behavior)
        // Set env vars that would be used as fallback
        std::env::set_var(
            "JWT_SECRET",
            "EnvVarSecret_Kx7mP@9nR!2sW#5vY$8bC&3fG*6jH^1kL%4pQ+7tZ-0uN~9dM=5eV(8xS)2wT!6yA#4zB",
        );

        // In dev environment, Vault is available, so it should succeed
        // Either from Vault (production) or env var fallback (development)
        let result = JwtConfig::new().await;

        match result {
            Ok(config) => {
                // Verify config was loaded successfully
                assert_eq!(config.jwt_issuer, "foxhunt-api");
                assert_eq!(config.jwt_audience, "foxhunt-services");
                assert!(config.jwt_secret.len() >= 64);

                // If Vault is available, the secret will be from Vault (not our env var)
                // If Vault is unavailable, it will use the env var as fallback
                // Both cases are valid and test the priority system
            }
            Err(e) => {
                // If both Vault AND env vars fail, report failure without panicking
                eprintln!("JwtConfig::new() failed with both Vault and env var: {e}");
                panic!("JwtConfig::new() should succeed with either Vault or env var available: {e}");
            }
        }

        // WAVE G23: Restore original env state
        match original_jwt_secret {
            Some(val) => std::env::set_var("JWT_SECRET", val),
            None => std::env::remove_var("JWT_SECRET"),
        }
    }
}
