//! Test utilities for TLI integration tests
//!
//! Provides JWT token generation and other test helpers.

use anyhow::Result;
use jsonwebtoken::{encode, EncodingKey, Header};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;

/// JWT claims structure for testing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestJwtClaims {
    /// JWT ID (unique identifier for revocation)
    pub jti: String,
    /// Subject (user ID)
    pub sub: String,
    /// Issued at timestamp
    pub iat: u64,
    /// Expiration timestamp
    pub exp: u64,
    /// Not before timestamp (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub nbf: Option<u64>,
    /// Issuer
    pub iss: String,
    /// Audience
    pub aud: String,
    /// User roles
    pub roles: Vec<String>,
    /// Permissions
    pub permissions: Vec<String>,
    /// Token type (access/refresh)
    pub token_type: String,
    /// Session ID (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub session_id: Option<String>,
}

/// Test JWT configuration
pub struct TestJwtConfig {
    pub secret: String,
    pub issuer: String,
    pub audience: String,
}

impl Default for TestJwtConfig {
    fn default() -> Self {
        Self {
            // Use same secret as API Gateway tests for compatibility
            secret: "test-secret-must-be-at-least-64-characters-long-for-security-validation-ok-1234567890".to_string(),
            issuer: "foxhunt-api".to_string(),
            audience: "foxhunt-services".to_string(),
        }
    }
}

/// Generate a valid JWT token for testing
///
/// Returns (token, jti) for token tracking in tests.
///
/// # Arguments
/// * `user_id` - User identifier (e.g., "user123")
/// * `roles` - User roles (e.g., vec!["trader".to_string()])
/// * `permissions` - User permissions (e.g., vec!["api.access".to_string()])
/// * `ttl_seconds` - Time to live in seconds (e.g., 3600 for 1 hour)
///
/// # Example
/// ```rust,ignore
/// let (token, jti) = generate_test_jwt_token(
///     "user123",
///     vec!["trader".to_string()],
///     vec!["api.access".to_string()],
///     3600,  // 1 hour
/// )?;
/// ```
pub fn generate_test_jwt_token(
    user_id: &str,
    roles: Vec<String>,
    permissions: Vec<String>,
    ttl_seconds: u64,
) -> Result<(String, String)> {
    let config = TestJwtConfig::default();
    let jti = Uuid::new_v4().to_string();

    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();

    let claims = TestJwtClaims {
        jti: jti.clone(),
        sub: user_id.to_string(),
        iat: now,
        exp: now + ttl_seconds,
        nbf: Some(now), // Not before: valid from now
        iss: config.issuer,
        aud: config.audience,
        roles,
        permissions,
        token_type: "access".to_string(),
        session_id: Some(Uuid::new_v4().to_string()),
    };

    let token = encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(config.secret.as_bytes()),
    )?;

    Ok((token, jti))
}

/// Generate an expired JWT token for testing token expiration logic
pub fn generate_expired_jwt_token(user_id: &str) -> Result<String> {
    let config = TestJwtConfig::default();

    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();

    let claims = TestJwtClaims {
        jti: Uuid::new_v4().to_string(),
        sub: user_id.to_string(),
        iat: now - 7200,       // Issued 2 hours ago
        exp: now - 3600,       // Expired 1 hour ago
        nbf: Some(now - 7200), // Not before: from 2 hours ago
        iss: config.issuer,
        aud: config.audience,
        roles: vec!["trader".to_string()],
        permissions: vec!["api.access".to_string()],
        token_type: "access".to_string(),
        session_id: Some(Uuid::new_v4().to_string()),
    };

    let token = encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(config.secret.as_bytes()),
    )?;

    Ok(token)
}

/// Generate a refresh token (similar to access token but with different type)
pub fn generate_test_refresh_token(user_id: &str, ttl_seconds: u64) -> Result<(String, String)> {
    let config = TestJwtConfig::default();
    let jti = Uuid::new_v4().to_string();

    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();

    let claims = TestJwtClaims {
        jti: jti.clone(),
        sub: user_id.to_string(),
        iat: now,
        exp: now + ttl_seconds,
        nbf: Some(now),
        iss: config.issuer,
        aud: config.audience,
        roles: vec!["trader".to_string()],
        permissions: vec!["api.access".to_string()],
        token_type: "refresh".to_string(),
        session_id: Some(Uuid::new_v4().to_string()),
    };

    let token = encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(config.secret.as_bytes()),
    )?;

    Ok((token, jti))
}

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

    #[test]
    fn test_generate_jwt_token_format() {
        let (token, jti) = generate_test_jwt_token(
            "test_user",
            vec!["trader".to_string()],
            vec!["api.access".to_string()],
            3600,
        )
        .unwrap();

        // JWT should have 3 parts (header.payload.signature)
        let parts: Vec<&str> = token.split('.').collect();
        assert_eq!(parts.len(), 3, "JWT should have 3 parts");

        // JTI should be a valid UUID
        assert!(Uuid::parse_str(&jti).is_ok(), "JTI should be valid UUID");
    }

    #[test]
    fn test_generate_expired_token() {
        let token = generate_expired_jwt_token("expired_user").unwrap();

        // JWT should have 3 parts
        let parts: Vec<&str> = token.split('.').collect();
        assert_eq!(parts.len(), 3, "JWT should have 3 parts");
    }

    #[test]
    fn test_generate_refresh_token() {
        let (token, jti) = generate_test_refresh_token("refresh_user", 7200).unwrap();

        // JWT should have 3 parts
        let parts: Vec<&str> = token.split('.').collect();
        assert_eq!(parts.len(), 3, "JWT should have 3 parts");

        // JTI should be a valid UUID
        assert!(Uuid::parse_str(&jti).is_ok(), "JTI should be valid UUID");
    }
}
