//! Authentication Flow Smoke Tests
//!
//! Tests to validate JWT authentication, session management, and authorization.

use super::common::*;
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey, Algorithm};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    iss: String,
    aud: String,
    exp: u64,
    iat: u64,
    roles: Vec<String>,
}

fn get_jwt_secret() -> String {
    std::env::var("JWT_SECRET")
        .unwrap_or_else(|_| "dev_secret_key_change_in_production".to_string())
}

fn create_test_token() -> Result<String, Box<dyn std::error::Error>> {
    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();

    let claims = Claims {
        sub: "test_user".to_string(),
        iss: "foxhunt-api".to_string(),
        aud: "foxhunt-services".to_string(),
        iat: now,
        exp: now + 3600, // Valid for 1 hour
        roles: vec!["trader".to_string()],
    };

    let token = encode(
        &Header::new(Algorithm::HS256),
        &claims,
        &EncodingKey::from_secret(get_jwt_secret().as_bytes()),
    )?;

    Ok(token)
}

#[tokio::test]
async fn test_jwt_token_generation() {
    let result = with_timeout(async {
        let token = create_test_token()
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        assert!(!token.is_empty(), "JWT token should not be empty");
        assert!(token.split('.').count() == 3, "JWT should have 3 parts");

        println!("✅ JWT token generation successful");
        println!("   Token length: {} characters", token.len());
        Ok(())
    }).await;

    assert!(result.is_ok(), "JWT token generation failed: {:?}", result.err());
}

#[tokio::test]
async fn test_jwt_token_validation() {
    let result = with_timeout(async {
        // Generate token
        let token = create_test_token()
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Validate token
        let validation = Validation::new(Algorithm::HS256);
        let token_data = decode::<Claims>(
            &token,
            &DecodingKey::from_secret(get_jwt_secret().as_bytes()),
            &validation,
        )
        .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        assert_eq!(token_data.claims.sub, "test_user");
        assert_eq!(token_data.claims.iss, "foxhunt-api");
        assert_eq!(token_data.claims.aud, "foxhunt-services");
        assert!(token_data.claims.roles.contains(&"trader".to_string()));

        println!("✅ JWT token validation successful");
        println!("   User: {}", token_data.claims.sub);
        println!("   Roles: {:?}", token_data.claims.roles);
        Ok(())
    }).await;

    assert!(result.is_ok(), "JWT token validation failed: {:?}", result.err());
}

#[tokio::test]
async fn test_jwt_token_expiration() {
    let result = with_timeout(async {
        // Create expired token
        let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();

        let expired_claims = Claims {
            sub: "test_user".to_string(),
            iss: "foxhunt-api".to_string(),
            aud: "foxhunt-services".to_string(),
            iat: now - 7200, // Issued 2 hours ago
            exp: now - 3600, // Expired 1 hour ago
            roles: vec!["trader".to_string()],
        };

        let expired_token = encode(
            &Header::new(Algorithm::HS256),
            &expired_claims,
            &EncodingKey::from_secret(get_jwt_secret().as_bytes()),
        )?;

        // Try to validate expired token
        let validation = Validation::new(Algorithm::HS256);
        let result = decode::<Claims>(
            &expired_token,
            &DecodingKey::from_secret(get_jwt_secret().as_bytes()),
            &validation,
        );

        assert!(result.is_err(), "Expired token should not validate");

        println!("✅ JWT expiration validation successful");
        Ok(())
    }).await;

    assert!(result.is_ok(), "JWT expiration test failed: {:?}", result.err());
}

#[tokio::test]
async fn test_jwt_invalid_signature() {
    let result = with_timeout(async {
        // Create token with one secret
        let token = create_test_token()
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Try to validate with different secret
        let validation = Validation::new(Algorithm::HS256);
        let wrong_secret = "wrong_secret_key";
        let result = decode::<Claims>(
            &token,
            &DecodingKey::from_secret(wrong_secret.as_bytes()),
            &validation,
        );

        assert!(result.is_err(), "Token with invalid signature should not validate");

        println!("✅ JWT signature validation successful");
        Ok(())
    }).await;

    assert!(result.is_ok(), "JWT signature validation test failed: {:?}", result.err());
}

#[tokio::test]
async fn test_redis_session_storage() {
    let result = with_timeout(async {
        use redis::AsyncCommands;

        let client = redis::Client::open(redis_url().as_str())
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        let mut con = client.get_multiplexed_async_connection()
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Simulate session storage
        let session_key = "smoke_test:session:test_user";
        let session_data = serde_json::json!({
            "user_id": "test_user",
            "roles": ["trader"],
            "created_at": chrono::Utc::now().to_rfc3339(),
        });

        // Store session with TTL
        con.set_ex::<_, _, ()>(session_key, session_data.to_string(), 3600)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Retrieve session
        let retrieved: String = con.get(session_key)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        let retrieved_json: serde_json::Value = serde_json::from_str(&retrieved)?;
        assert_eq!(retrieved_json["user_id"], "test_user");

        // Cleanup
        con.del::<_, ()>(session_key)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        println!("✅ Redis session storage successful");
        Ok(())
    }).await;

    skip_if_unavailable!("Redis", { result });
}

#[tokio::test]
async fn test_jwt_revocation_check() {
    let result = with_timeout(async {
        use redis::AsyncCommands;

        let client = redis::Client::open(redis_url().as_str())
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        let mut con = client.get_multiplexed_async_connection()
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Create token
        let token = create_test_token()
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Simulate token revocation
        let jti = "smoke_test_token_id";
        let revocation_key = format!("revoked_tokens:{}", jti);

        // Add to revocation list
        con.set_ex::<_, _, ()>(&revocation_key, "revoked", 3600)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Check if token is revoked
        let is_revoked: bool = con.exists(&revocation_key)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        assert!(is_revoked, "Token should be marked as revoked");

        // Cleanup
        con.del::<_, ()>(&revocation_key)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        println!("✅ JWT revocation check successful");
        Ok(())
    }).await;

    skip_if_unavailable!("Redis", { result });
}

#[tokio::test]
async fn test_rate_limiting() {
    let result = with_timeout(async {
        use redis::AsyncCommands;

        let client = redis::Client::open(redis_url().as_str())
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        let mut con = client.get_multiplexed_async_connection()
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        // Simulate rate limiting
        let rate_limit_key = "smoke_test:rate_limit:test_user";
        let window_seconds = 60;
        let max_requests = 100;

        // Increment counter
        let count: i32 = con.incr(&rate_limit_key, 1)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        if count == 1 {
            // Set expiration on first request
            con.expire::<_, ()>(&rate_limit_key, window_seconds)
                .await
                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
        }

        // Check if rate limit exceeded
        let is_limited = count > max_requests;
        assert!(!is_limited, "Rate limit should not be exceeded on first request");

        // Get TTL
        let ttl: i32 = con.ttl(&rate_limit_key)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        assert!(ttl > 0 && ttl <= window_seconds, "TTL should be set correctly");

        // Cleanup
        con.del::<_, ()>(&rate_limit_key)
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

        println!("✅ Rate limiting check successful");
        println!("   Request count: {}/{}", count, max_requests);
        println!("   TTL: {} seconds", ttl);
        Ok(())
    }).await;

    skip_if_unavailable!("Redis", { result });
}

#[tokio::test]
async fn test_authentication_flow_complete() {
    println!("🔐 Running complete authentication flow...");

    let mut steps_passed = 0;
    let total_steps = 4;

    // Step 1: Generate token
    if create_test_token().is_ok() {
        println!("  ✓ JWT token generation");
        steps_passed += 1;
    } else {
        println!("  ✗ JWT token generation failed");
    }

    // Step 2: Validate token
    if let Ok(token) = create_test_token() {
        let validation = Validation::new(Algorithm::HS256);
        if decode::<Claims>(
            &token,
            &DecodingKey::from_secret(get_jwt_secret().as_bytes()),
            &validation,
        ).is_ok() {
            println!("  ✓ JWT token validation");
            steps_passed += 1;
        } else {
            println!("  ✗ JWT token validation failed");
        }
    }

    // Step 3: Session storage
    if let Ok(client) = redis::Client::open(redis_url().as_str()) {
        if let Ok(mut con) = client.get_multiplexed_async_connection().await {
            use redis::AsyncCommands;
            let test_key = "smoke_test:auth_flow:session";
            if con.set_ex::<_, _, ()>(test_key, "test", 60).await.is_ok() {
                println!("  ✓ Session storage");
                steps_passed += 1;
                let _ = con.del::<_, ()>(test_key).await;
            }
        }
    } else {
        println!("  ⚠ Session storage (Redis unavailable)");
    }

    // Step 4: Rate limiting
    if let Ok(client) = redis::Client::open(redis_url().as_str()) {
        if let Ok(mut con) = client.get_multiplexed_async_connection().await {
            use redis::AsyncCommands;
            let test_key = "smoke_test:auth_flow:rate_limit";
            if con.incr::<_, _, i32>(test_key, 1).await.is_ok() {
                println!("  ✓ Rate limiting");
                steps_passed += 1;
                let _ = con.del::<_, ()>(test_key).await;
            }
        }
    } else {
        println!("  ⚠ Rate limiting (Redis unavailable)");
    }

    println!("\n📊 Authentication Flow Summary:");
    println!("   Steps passed: {}/{}", steps_passed, total_steps);

    assert!(steps_passed >= 2, "Authentication flow requires at least JWT functionality");
    println!("✅ Authentication flow check complete");
}
