//! Common test utilities for API Gateway integration tests

#![allow(dead_code)]

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

pub use api::auth::{Jti, JwtClaims};

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

impl Default for TestJwtConfig {
    fn default() -> Self {
        Self {
            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
pub fn generate_test_token(
    user_id: &str,
    roles: Vec<String>,
    permissions: Vec<String>,
    ttl_seconds: u64,
) -> Result<(String, String)> {
    let config = TestJwtConfig::default();
    let jti = Jti::new();

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

    let claims = JwtClaims {
        jti: jti.0.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.0))
}

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

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

    let claims = JwtClaims {
        jti: Jti::new().0,
        sub: user_id.to_string(),
        iat: now - 7200,
        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 token with invalid signature
pub fn generate_invalid_signature_token(user_id: &str) -> Result<String> {
    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();

    let claims = JwtClaims {
        jti: Jti::new().0,
        sub: user_id.to_string(),
        iat: now,
        exp: now + 3600,
        nbf: Some(now), // Not before: valid from now
        iss: "foxhunt-api".to_string(),
        aud: "foxhunt-services".to_string(),
        roles: vec!["trader".to_string()],
        permissions: vec!["api.access".to_string()],
        token_type: "access".to_string(),
        session_id: Some(Uuid::new_v4().to_string()),
    };

    // Use wrong secret to create invalid signature
    let token = encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(b"wrong-secret-key-that-will-fail-validation-checks-completely"),
    )?;

    Ok(token)
}

/// Wait for Redis to be ready with proper timeout handling
///
/// CRITICAL FIX: The redis crate v0.27.6 does NOT support connection_timeout
/// or response_timeout as URL parameters. These are silently ignored, causing
/// 60+ second OS-level TCP timeouts when Redis is unavailable.
///
/// Solution: Use tokio::time::timeout() to wrap async operations.
pub async fn wait_for_redis(redis_url: &str, max_attempts: usize) -> Result<()> {
    use tokio::time::{timeout, Duration};

    for attempt in 1..=max_attempts {
        match redis::Client::open(redis_url) {
            Ok(client) => {
                // CRITICAL: Wrap connection with tokio timeout (2s)
                match timeout(
                    Duration::from_secs(2),
                    client.get_multiplexed_async_connection()
                ).await {
                    Ok(Ok(mut conn)) => {
                        // CRITICAL: Wrap PING command with tokio timeout (1s)
                        match timeout(
                            Duration::from_secs(1),
                            redis::cmd("PING").query_async::<String>(&mut conn)
                        ).await {
                            Ok(Ok(_)) => {
                                println!("✓ Redis ready after {} attempts", attempt);
                                return Ok(());
                            },
                            Ok(Err(e)) => {
                                if attempt == max_attempts {
                                    return Err(anyhow::anyhow!("Redis PING failed: {}", e));
                                }
                            },
                            Err(_) => {
                                if attempt == max_attempts {
                                    return Err(anyhow::anyhow!("Redis PING timeout after 1s"));
                                }
                            },
                        }
                    },
                    Ok(Err(e)) => {
                        if attempt == max_attempts {
                            return Err(anyhow::anyhow!("Redis connection failed: {}", e));
                        }
                    },
                    Err(_) => {
                        if attempt == max_attempts {
                            return Err(anyhow::anyhow!("Redis connection timeout after 2s"));
                        }
                    },
                }
            },
            Err(e) => {
                if attempt == max_attempts {
                    return Err(anyhow::anyhow!("Failed to create Redis client: {}", e));
                }
            },
        }
        tokio::time::sleep(Duration::from_millis(100)).await;
    }

    Err(anyhow::anyhow!(
        "Redis not ready after {} attempts",
        max_attempts
    ))
}

/// Clean up Redis test data with proper timeout handling
pub async fn cleanup_redis(redis_url: &str) -> Result<()> {
    use tokio::time::{timeout, Duration};

    let client = redis::Client::open(redis_url)?;

    // CRITICAL: Wrap connection with tokio timeout (2s)
    let mut conn = timeout(
        Duration::from_secs(2),
        client.get_multiplexed_async_connection()
    )
    .await
    .context("Redis connection timed out after 2s")?
    .context("Failed to connect to Redis")?;

    // CRITICAL: Wrap FLUSHDB with tokio timeout (1s)
    timeout(
        Duration::from_secs(1),
        redis::cmd("FLUSHDB").query_async::<()>(&mut conn)
    )
    .await
    .context("FLUSHDB timeout after 1s")?
    .context("FLUSHDB failed")?;

    Ok(())
}
