//! Tests for JWT Authentication Helper Module
//!
//! Validates that the auth_helpers module correctly generates JWT tokens
//! and creates authenticated clients for E2E testing.

mod common;

use anyhow::Result;
use common::auth_helpers::{
    create_default_test_jwt, create_expired_test_jwt, create_invalid_issuer_jwt, create_test_jwt,
    get_api_addr, get_test_jwt_secret, get_test_user_id, TestAuthConfig,
    DEFAULT_API_GATEWAY_ADDR, DEFAULT_TEST_USER_ID,
};

#[test]
fn test_get_test_jwt_secret() {
    let secret = get_test_jwt_secret();
    assert!(!secret.is_empty());
    assert!(
        secret.len() >= 64,
        "JWT secret should be at least 64 characters"
    );
    // Note: JWT_SECRET may be set in environment, so we don't assert exact value
}

#[test]
fn test_get_test_user_id() {
    let user_id = get_test_user_id();
    assert_eq!(user_id, DEFAULT_TEST_USER_ID);
}

#[test]
fn test_get_api_addr() {
    let addr = get_api_addr();
    assert!(!addr.is_empty());
    assert!(addr.starts_with("http://"));
    assert_eq!(addr, DEFAULT_API_GATEWAY_ADDR);
}

#[test]
fn test_create_default_test_jwt() -> Result<()> {
    let token = create_default_test_jwt()?;
    assert!(!token.is_empty());
    assert_eq!(
        token.matches('.').count(),
        2,
        "JWT should have 3 parts separated by 2 dots"
    );

    // Verify token structure (header.payload.signature)
    let parts: Vec<&str> = token.split('.').collect();
    assert_eq!(parts.len(), 3);
    assert!(!parts[0].is_empty(), "Header should not be empty");
    assert!(!parts[1].is_empty(), "Payload should not be empty");
    assert!(!parts[2].is_empty(), "Signature should not be empty");

    Ok(())
}

#[test]
fn test_create_test_jwt_trader() -> Result<()> {
    let config = TestAuthConfig::trader();
    let token = create_test_jwt(config)?;

    assert!(!token.is_empty());
    assert_eq!(token.matches('.').count(), 2);

    // Decode and verify claims
    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let token_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    assert_eq!(token_data.claims.sub, DEFAULT_TEST_USER_ID);
    assert_eq!(token_data.claims.iss, "foxhunt-trading");
    assert_eq!(token_data.claims.aud, "trading-api");
    assert!(token_data.claims.roles.contains(&"trader".to_string()));
    assert!(!token_data.claims.jti.is_empty());

    Ok(())
}

#[test]
fn test_create_test_jwt_admin() -> Result<()> {
    let config = TestAuthConfig::admin();
    let token = create_test_jwt(config)?;

    assert!(!token.is_empty());

    // Decode and verify admin claims
    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let token_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    assert_eq!(token_data.claims.sub, "test_admin_001");
    assert!(token_data.claims.roles.contains(&"admin".to_string()));
    assert!(token_data
        .claims
        .permissions
        .iter()
        .any(|p| p.contains("admin")));

    Ok(())
}

#[test]
fn test_create_test_jwt_viewer() -> Result<()> {
    let config = TestAuthConfig::viewer();
    let token = create_test_jwt(config)?;

    assert!(!token.is_empty());

    // Decode and verify viewer claims
    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let token_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    assert_eq!(token_data.claims.sub, "test_viewer_001");
    assert!(token_data.claims.roles.contains(&"viewer".to_string()));
    assert!(token_data
        .claims
        .permissions
        .iter()
        .any(|p| p.contains("view")));
    assert!(!token_data
        .claims
        .permissions
        .iter()
        .any(|p| p.contains("submit")));

    Ok(())
}

#[test]
fn test_create_expired_test_jwt() -> Result<()> {
    let token = create_expired_test_jwt()?;
    assert!(!token.is_empty());

    // Decode without expiry validation to check the token
    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.validate_exp = false; // Disable expiry check
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let token_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    // Verify the token is actually expired
    let now = chrono::Utc::now().timestamp() as usize;
    assert!(
        token_data.claims.exp < now,
        "Token should be expired (exp: {}, now: {})",
        token_data.claims.exp,
        now
    );

    Ok(())
}

#[test]
fn test_create_invalid_issuer_jwt() -> Result<()> {
    let token = create_invalid_issuer_jwt()?;
    assert!(!token.is_empty());

    // Try to decode with correct issuer validation - should fail
    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let result = decode::<common::auth_helpers::TestJwtClaims>(
        &token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    );

    assert!(
        result.is_err(),
        "Should fail validation due to wrong issuer"
    );

    Ok(())
}

#[test]
fn test_auth_config_builder_pattern() {
    let config = TestAuthConfig::trader()
        .with_user_id("custom_user_123")
        .with_roles(vec!["custom_role".to_string()])
        .with_permissions(vec!["custom.permission".to_string()])
        .with_expiry(chrono::Duration::minutes(30))
        .with_mfa_enabled();

    assert_eq!(config.user_id, "custom_user_123");
    assert_eq!(config.roles, vec!["custom_role"]);
    assert_eq!(config.permissions, vec!["custom.permission"]);
    assert_eq!(config.expiry_duration, chrono::Duration::minutes(30));
    assert!(config.mfa_enabled);
    assert!(config.mfa_verified);
}

#[test]
fn test_auth_config_mfa_unverified() {
    let config = TestAuthConfig::trader().with_mfa_unverified();

    assert!(config.mfa_enabled);
    assert!(!config.mfa_verified);
}

#[test]
fn test_default_auth_config() {
    let config = TestAuthConfig::default();

    assert_eq!(config.user_id, DEFAULT_TEST_USER_ID);
    assert!(config.roles.contains(&"trader".to_string()));
    assert!(config.permissions.contains(&"api.access".to_string()));
    assert!(config.permissions.contains(&"trading.submit".to_string()));
    assert!(!config.mfa_enabled);
}

#[test]
fn test_jwt_token_has_required_claims() -> Result<()> {
    let config = TestAuthConfig::trader();
    let token = create_test_jwt(config)?;

    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let token_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    // Verify all required claims are present
    assert!(
        !token_data.claims.jti.is_empty(),
        "jti (JWT ID) is required"
    );
    assert!(
        !token_data.claims.sub.is_empty(),
        "sub (subject) is required"
    );
    assert!(token_data.claims.exp > 0, "exp (expiry) is required");
    assert!(token_data.claims.iat > 0, "iat (issued at) is required");
    assert!(
        !token_data.claims.iss.is_empty(),
        "iss (issuer) is required"
    );
    assert!(
        !token_data.claims.aud.is_empty(),
        "aud (audience) is required"
    );
    assert!(!token_data.claims.roles.is_empty(), "roles are required");
    assert_eq!(token_data.claims.token_type, "access");
    assert!(token_data.claims.session_id.is_some());

    Ok(())
}

#[test]
fn test_jwt_expiry_is_future() -> Result<()> {
    let token = create_default_test_jwt()?;

    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let token_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    let now = chrono::Utc::now().timestamp() as usize;
    assert!(token_data.claims.exp > now, "Token should not be expired");
    assert!(
        token_data.claims.iat <= now,
        "Token should be issued in the past or now"
    );

    Ok(())
}

#[test]
fn test_multiple_tokens_have_unique_jti() -> Result<()> {
    let config = TestAuthConfig::trader();
    let token1 = create_test_jwt(config.clone())?;
    let token2 = create_test_jwt(config)?;

    use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["foxhunt-trading"]);
    validation.set_audience(&["trading-api"]);

    let jwt_secret = get_test_jwt_secret();
    let token1_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token1,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    let token2_data = decode::<common::auth_helpers::TestJwtClaims>(
        &token2,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &validation,
    )?;

    assert_ne!(
        token1_data.claims.jti, token2_data.claims.jti,
        "Each token should have a unique JTI"
    );

    Ok(())
}
