//! JWT Configuration Management with Vault Integration
//!
//! This module provides secure JWT configuration management with support for:
//! - Loading JWT secrets from HashiCorp Vault (production)
//! - Fallback to environment variables (development)
//! - Secret validation (minimum 64 characters, entropy checks)
//! - Graceful rotation support
//!
//! # Security
//! - JWT secrets are wrapped in `SecretString` to prevent exposure
//! - Secrets are automatically zeroized when dropped
//! - Vault integration enforces secure secret storage
//! - Environment fallback is only for development

use anyhow::{Context, Result};
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize};
use std::fmt;
use tracing::{debug, info, warn};
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};

/// JWT Configuration with secure secret management
#[derive(Clone, Serialize, Deserialize)]
pub struct JwtConfig {
    /// JWT signing secret (securely stored)
    #[serde(
        serialize_with = "serialize_secret",
        deserialize_with = "deserialize_secret"
    )]
    pub jwt_secret: SecretString,

    /// JWT issuer (e.g., "foxhunt-api")
    pub jwt_issuer: String,

    /// JWT audience (e.g., "foxhunt-services")
    pub jwt_audience: String,

    /// Secret rotation date (for tracking)
    pub rotation_date: Option<String>,
}

/// Custom serializer for SecretString that prevents secret exposure
fn serialize_secret<S>(_secret: &SecretString, serializer: S) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    serializer.serialize_str("***REDACTED***")
}

/// Custom deserializer for SecretString
fn deserialize_secret<'de, D>(deserializer: D) -> Result<SecretString, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    Ok(SecretString::from(s))
}

impl fmt::Debug for JwtConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("JwtConfig")
            .field("jwt_secret", &"***REDACTED***")
            .field("jwt_issuer", &self.jwt_issuer)
            .field("jwt_audience", &self.jwt_audience)
            .field("rotation_date", &self.rotation_date)
            .finish()
    }
}

impl JwtConfig {
    /// Load JWT configuration from Vault (production) or environment (development)
    ///
    /// # Priority
    /// 1. Vault (secret/foxhunt/jwt) - Production
    /// 2. Environment variables (JWT_SECRET, JWT_ISSUER, JWT_AUDIENCE) - Development
    /// 3. .env file - Local development
    ///
    /// # Errors
    /// Returns error if JWT secret cannot be loaded or validation fails
    pub async fn load() -> Result<Self> {
        // Try Vault first (production)
        if let Ok(config) = Self::load_from_vault().await {
            info!("✅ JWT configuration loaded from Vault");
            return Ok(config);
        }

        // Fallback to environment variables (development)
        warn!("⚠️  Vault unavailable - falling back to environment variables (development only)");
        Self::load_from_env()
    }

    /// Load JWT configuration from HashiCorp Vault
    ///
    /// Reads from `secret/foxhunt/jwt` with keys:
    /// - jwt_secret: The signing secret (minimum 64 characters)
    /// - jwt_issuer: Token issuer
    /// - jwt_audience: Token audience
    /// - rotation_date: Optional rotation tracking
    async fn load_from_vault() -> Result<Self> {
        let vault_addr =
            std::env::var("VAULT_ADDR").unwrap_or_else(|_| "http://localhost:8200".to_string());

        let vault_token = std::env::var("VAULT_TOKEN")
            .context("VAULT_TOKEN not set - required for production JWT configuration")?;

        debug!("Connecting to Vault at {}", vault_addr);

        let client = VaultClient::new(
            VaultClientSettingsBuilder::default()
                .address(&vault_addr)
                .token(&vault_token)
                .build()
                .context("Failed to build Vault client settings")?,
        )
        .context("Failed to create Vault client")?;

        // Read JWT configuration from secret/foxhunt/jwt
        let secret: std::collections::HashMap<String, String> =
            vaultrs::kv2::read(&client, "secret", "foxhunt/jwt")
                .await
                .context("Failed to read JWT secret from Vault at secret/foxhunt/jwt")?;

        let jwt_secret = secret
            .get("jwt_secret")
            .context("jwt_secret not found in Vault")?
            .clone();

        let jwt_issuer = secret
            .get("jwt_issuer")
            .context("jwt_issuer not found in Vault")?
            .clone();

        let jwt_audience = secret
            .get("jwt_audience")
            .context("jwt_audience not found in Vault")?
            .clone();

        let rotation_date = secret.get("rotation_date").cloned();

        let config = Self {
            jwt_secret: SecretString::from(jwt_secret),
            jwt_issuer,
            jwt_audience,
            rotation_date,
        };

        // Validate secret strength
        config.validate()?;

        Ok(config)
    }

    /// Load JWT configuration from environment variables (development fallback)
    ///
    /// Reads from:
    /// - JWT_SECRET: Signing secret (minimum 64 characters)
    /// - JWT_ISSUER: Token issuer (default: "foxhunt-api")
    /// - JWT_AUDIENCE: Token audience (default: "foxhunt-services")
    fn load_from_env() -> Result<Self> {
        let jwt_secret = std::env::var("JWT_SECRET").context(
            "JWT_SECRET not set. Production: use Vault. Development: set JWT_SECRET env var",
        )?;

        let jwt_issuer =
            std::env::var("JWT_ISSUER").unwrap_or_else(|_| "foxhunt-api".to_string());

        let jwt_audience =
            std::env::var("JWT_AUDIENCE").unwrap_or_else(|_| "foxhunt-services".to_string());

        let config = Self {
            jwt_secret: SecretString::from(jwt_secret),
            jwt_issuer,
            jwt_audience,
            rotation_date: None,
        };

        // Validate secret strength
        config.validate()?;

        warn!("⚠️  Using JWT_SECRET from environment - not recommended for production");

        Ok(config)
    }

    /// Validate JWT secret strength
    ///
    /// Requirements:
    /// - Minimum 64 characters (512-bit security)
    /// - High entropy (checked via character variety)
    ///
    /// # Errors
    /// Returns error if validation fails
    pub fn validate(&self) -> Result<()> {
        let secret = self.jwt_secret.expose_secret();

        // Check minimum length (64 chars = 512 bits for base64)
        if secret.len() < 64 {
            anyhow::bail!(
                "JWT secret must be at least 64 characters (current: {}). \
                Generate with: openssl rand -base64 64 | tr -d '\\n'",
                secret.len()
            );
        }

        // Check for basic entropy (not a weak pattern)
        Self::check_entropy(secret)?;

        Ok(())
    }

    /// Check secret entropy to detect weak patterns
    fn check_entropy(secret: &str) -> Result<()> {
        // Check for character variety
        let has_upper = secret.chars().any(|c| c.is_uppercase());
        let has_lower = secret.chars().any(|c| c.is_lowercase());
        let has_digit = secret.chars().any(|c| c.is_ascii_digit());
        let has_special = secret.chars().any(|c| !c.is_alphanumeric());

        let variety_count = [has_upper, has_lower, has_digit, has_special]
            .iter()
            .filter(|&&x| x)
            .count();

        if variety_count < 3 {
            anyhow::bail!(
                "JWT secret has insufficient entropy (character variety). \
                Must include at least 3 of: uppercase, lowercase, digits, special characters. \
                Generate with: openssl rand -base64 64 | tr -d '\\n'"
            );
        }

        // Check for repeated patterns (like "aaaaa" or "11111")
        let mut prev_char = '\0';
        let mut repeat_count = 0;
        let mut max_repeat = 0;

        for c in secret.chars() {
            if c == prev_char {
                repeat_count += 1;
                max_repeat = max_repeat.max(repeat_count);
            } else {
                repeat_count = 1;
            }
            prev_char = c;
        }

        if max_repeat > 5 {
            anyhow::bail!(
                "JWT secret contains repeated character patterns (max repeat: {}). \
                Generate a cryptographically secure secret with: openssl rand -base64 64 | tr -d '\\n'",
                max_repeat
            );
        }

        Ok(())
    }

    /// Get JWT secret for signing (requires explicit exposure)
    ///
    /// # Security
    /// This method requires the caller to explicitly expose the secret.
    /// Use only when necessary (e.g., JWT signing) and ensure the exposed
    /// value is not logged or stored insecurely.
    pub fn secret(&self) -> &SecretString {
        &self.jwt_secret
    }

    /// Get JWT issuer
    pub fn issuer(&self) -> &str {
        &self.jwt_issuer
    }

    /// Get JWT audience
    pub fn audience(&self) -> &str {
        &self.jwt_audience
    }
}

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

    #[test]
    fn test_jwt_config_validation_success() {
        let config = JwtConfig {
            jwt_secret: SecretString::from(
                "JcqslC17wjp3hG/O1bHLwsVS7CfmfbJuXccnJ4XFJMeC3dhV1s46C4NhmDNCHK/o+7j7ok5uYJdqGcOU+NhBSA==".to_string()
            ),
            jwt_issuer: "foxhunt-api".to_string(),
            jwt_audience: "foxhunt-services".to_string(),
            rotation_date: Some("2025-10-18".to_string()),
        };

        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_jwt_config_validation_too_short() {
        let config = JwtConfig {
            jwt_secret: SecretString::from("short_secret_32chars_only!!!!!".to_string()),
            jwt_issuer: "foxhunt-api".to_string(),
            jwt_audience: "foxhunt-services".to_string(),
            rotation_date: None,
        };

        let result = config.validate();
        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("at least 64 characters"));
    }

    #[test]
    fn test_jwt_config_validation_low_entropy() {
        let weak_secret = "a".repeat(70); // 70 chars but all same character
        let config = JwtConfig {
            jwt_secret: SecretString::from(weak_secret),
            jwt_issuer: "foxhunt-api".to_string(),
            jwt_audience: "foxhunt-services".to_string(),
            rotation_date: None,
        };

        let result = config.validate();
        assert!(result.is_err());
    }

    #[test]
    fn test_jwt_config_debug_redacts_secret() {
        let config = JwtConfig {
            jwt_secret: SecretString::from("test-secret-should-not-appear".to_string()),
            jwt_issuer: "foxhunt-api".to_string(),
            jwt_audience: "foxhunt-services".to_string(),
            rotation_date: None,
        };

        let debug_str = format!("{:?}", config);
        assert!(debug_str.contains("***REDACTED***"));
        assert!(!debug_str.contains("test-secret-should-not-appear"));
    }

    #[test]
    fn test_jwt_config_accessors() {
        let config = JwtConfig {
            jwt_secret: SecretString::from("test-secret".to_string()),
            jwt_issuer: "test-issuer".to_string(),
            jwt_audience: "test-audience".to_string(),
            rotation_date: Some("2025-10-18".to_string()),
        };

        assert_eq!(config.issuer(), "test-issuer");
        assert_eq!(config.audience(), "test-audience");
        assert_eq!(config.secret().expose_secret(), "test-secret");
    }
}
