//! Regime Detection gRPC Integration Tests
//!
//! Tests for Wave D regime detection endpoints:
//! - GetRegimeState: Current regime state for a symbol
//! - GetRegimeTransitions: Historical regime transitions
//!
//! **IMPORTANT**: These tests require a running Trading Service instance.
//! Run with: `cargo test -p trading_service --test regime_grpc_integration_test -- --ignored`
//!
//! Setup:
//! 1. Start services: `docker-compose up -d`
//! 2. Start Trading Service: `cargo run -p trading_service --bin trading_service --release &`
//! 3. Wait for startup: `sleep 5`
//! 4. Run tests: `cargo test -p trading_service --test regime_grpc_integration_test -- --ignored`

#![allow(unused_crate_dependencies, clippy::expect_fun_call)]

mod common;

use common::auth_helpers::{create_test_jwt, TestAuthConfig};
use tonic::metadata::MetadataValue;
use tonic::transport::Channel;
use tonic::{Request, Status};
use trading_service::proto::trading::trading_service_client::TradingServiceClient;
use trading_service::proto::trading::{GetRegimeStateRequest, GetRegimeTransitionsRequest};

/// Helper function to create an authenticated gRPC client
async fn create_client() -> Result<
    TradingServiceClient<
        tonic::service::interceptor::InterceptedService<
            Channel,
            impl Fn(Request<()>) -> Result<Request<()>, Status> + Clone,
        >,
    >,
    Box<dyn std::error::Error>,
> {
    // Create JWT token with trader permissions
    let config = TestAuthConfig::trader()
        .with_user_id("test_trader_001")
        .with_roles(vec!["trader".to_string()])
        .with_permissions(vec![
            "api.access".to_string(),
            "trading.submit".to_string(),
            "trading.view".to_string(),
        ]);

    let token = create_test_jwt(config.clone())?;
    let user_id = config.user_id.clone();
    let roles_str = config.roles.join(",");

    // Connect to Trading Service directly
    let channel = Channel::from_static("http://localhost:50052")
        .connect()
        .await?;

    // Create interceptor that injects JWT token and user context
    let interceptor = move |mut req: Request<()>| -> Result<Request<()>, Status> {
        // JWT token in authorization header
        let token_value = format!("Bearer {}", token);
        let metadata_value = MetadataValue::try_from(token_value)
            .map_err(|_| Status::internal("Failed to create metadata value"))?;
        req.metadata_mut().insert("authorization", metadata_value);

        // User context in metadata headers
        let user_id_value = MetadataValue::try_from(user_id.clone())
            .map_err(|_| Status::internal("Failed to create user_id metadata"))?;
        req.metadata_mut().insert("x-user-id", user_id_value);

        let role_value = MetadataValue::try_from(roles_str.clone())
            .map_err(|_| Status::internal("Failed to create role metadata"))?;
        req.metadata_mut().insert("x-user-role", role_value);

        Ok(req)
    };

    Ok(TradingServiceClient::with_interceptor(channel, interceptor))
}

// ==================== REGIME STATE TESTS ====================

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_get_regime_state_es_fut() {
    // Test GetRegimeState for ES.FUT
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    let request = Request::new(GetRegimeStateRequest {
        symbol: "ES.FUT".to_string(),
    });

    let response = client
        .get_regime_state(request)
        .await
        .expect("GetRegimeState RPC failed");

    let regime_state = response.into_inner();

    // Validate response structure
    assert_eq!(regime_state.symbol, "ES.FUT");
    assert!(!regime_state.current_regime.is_empty());
    assert!(["NORMAL", "TRENDING", "RANGING", "VOLATILE", "CRISIS"]
        .contains(&regime_state.current_regime.to_uppercase().as_str()));
    assert!(regime_state.confidence >= 0.0 && regime_state.confidence <= 1.0);
    assert!(regime_state.updated_at > 0);
    assert!(regime_state.stability >= 0.0 && regime_state.stability <= 1.0);
    assert!(regime_state.entropy >= 0.0 && regime_state.entropy <= 1.0);

    println!(
        "✅ GetRegimeState ES.FUT: regime={}, confidence={:.2}, ADX={:.2}, stability={:.2}",
        regime_state.current_regime,
        regime_state.confidence,
        regime_state.adx,
        regime_state.stability
    );
}

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_get_regime_state_nq_fut() {
    // Test GetRegimeState for NQ.FUT
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    let request = Request::new(GetRegimeStateRequest {
        symbol: "NQ.FUT".to_string(),
    });

    let response = client
        .get_regime_state(request)
        .await
        .expect("GetRegimeState RPC failed");

    let regime_state = response.into_inner();

    assert_eq!(regime_state.symbol, "NQ.FUT");
    assert!(!regime_state.current_regime.is_empty());
    assert!(regime_state.confidence >= 0.0 && regime_state.confidence <= 1.0);

    println!(
        "✅ GetRegimeState NQ.FUT: regime={}, confidence={:.2}",
        regime_state.current_regime, regime_state.confidence
    );
}

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_get_regime_state_invalid_symbol() {
    // Test GetRegimeState with invalid symbol (should return error or default state)
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    let request = Request::new(GetRegimeStateRequest {
        symbol: "INVALID.SYM".to_string(),
    });

    let result = client.get_regime_state(request).await;

    // Either error or default state with low confidence
    match result {
        Ok(response) => {
            let state = response.into_inner();
            println!(
                "⚠️  Invalid symbol returned default state: regime={}, confidence={:.2}",
                state.current_regime, state.confidence
            );
            assert!(state.confidence < 0.5); // Low confidence for unknown symbols
        },
        Err(e) => {
            println!("✅ Invalid symbol correctly rejected: {:?}", e);
        },
    }
}

// ==================== REGIME TRANSITIONS TESTS ====================

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_get_regime_transitions_es_fut() {
    // Test GetRegimeTransitions for ES.FUT with limit
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    let request = Request::new(GetRegimeTransitionsRequest {
        symbol: "ES.FUT".to_string(),
        limit: 10,
    });

    let response = client
        .get_regime_transitions(request)
        .await
        .expect("GetRegimeTransitions RPC failed");

    let transitions = response.into_inner().transitions;

    // Validate response
    assert!(
        !transitions.is_empty(),
        "No transitions returned for ES.FUT"
    );
    assert!(
        transitions.len() <= 10,
        "Returned more than requested limit"
    );

    // Validate first transition
    let first = &transitions[0];
    assert!(!first.from_regime.is_empty());
    assert!(!first.to_regime.is_empty());
    assert!(first.transition_probability >= 0.0 && first.transition_probability <= 1.0);
    assert!(first.timestamp > 0);
    assert!(first.duration_bars >= 0);

    println!("✅ GetRegimeTransitions ES.FUT: {} transitions, latest: {} → {} (probability={:.2}, duration={} bars)",
        transitions.len(),
        first.from_regime,
        first.to_regime,
        first.transition_probability,
        first.duration_bars
    );
}

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_get_regime_transitions_large_limit() {
    // Test GetRegimeTransitions with large limit
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    let request = Request::new(GetRegimeTransitionsRequest {
        symbol: "ES.FUT".to_string(),
        limit: 100,
    });

    let response = client
        .get_regime_transitions(request)
        .await
        .expect("GetRegimeTransitions RPC failed");

    let transitions = response.into_inner().transitions;

    assert!(
        !transitions.is_empty(),
        "No transitions returned for large limit"
    );
    assert!(
        transitions.len() <= 100,
        "Returned more than requested limit"
    );

    // Validate transitions are sorted by timestamp (descending)
    for i in 1..transitions.len() {
        assert!(
            transitions[i - 1].timestamp >= transitions[i].timestamp,
            "Transitions not sorted by timestamp descending"
        );
    }

    println!(
        "✅ GetRegimeTransitions with limit=100: {} transitions returned",
        transitions.len()
    );
}

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_get_regime_transitions_multiple_symbols() {
    // Test GetRegimeTransitions for multiple symbols
    let symbols = vec!["ES.FUT", "NQ.FUT", "CL.FUT"];
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    for symbol in symbols {
        let request = Request::new(GetRegimeTransitionsRequest {
            symbol: symbol.to_string(),
            limit: 5,
        });

        let response = client
            .get_regime_transitions(request)
            .await
            .expect(&format!("GetRegimeTransitions failed for {}", symbol));

        let transitions = response.into_inner().transitions;

        println!("✅ {}: {} transitions", symbol, transitions.len());

        // Validate transition structure
        for transition in &transitions {
            assert!(!transition.from_regime.is_empty());
            assert!(!transition.to_regime.is_empty());
        }
    }
}

// ==================== PERFORMANCE TESTS ====================

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_regime_state_performance() {
    // Test GetRegimeState performance (should be <10ms)
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    let mut latencies = Vec::new();

    for _ in 0..100 {
        let request = Request::new(GetRegimeStateRequest {
            symbol: "ES.FUT".to_string(),
        });

        let start = std::time::Instant::now();
        let _response = client
            .get_regime_state(request)
            .await
            .expect("GetRegimeState RPC failed");
        let latency = start.elapsed();

        latencies.push(latency);
    }

    // Calculate statistics
    let avg_latency: std::time::Duration =
        latencies.iter().sum::<std::time::Duration>() / latencies.len() as u32;
    let mut sorted = latencies.clone();
    sorted.sort();
    let p50 = sorted[sorted.len() / 2];
    let p99 = sorted[sorted.len() * 99 / 100];

    println!("✅ GetRegimeState Performance (100 requests):");
    println!("   Average: {:?}", avg_latency);
    println!("   P50: {:?}", p50);
    println!("   P99: {:?}", p99);

    // Performance targets: P99 < 10ms
    assert!(
        p99 < std::time::Duration::from_millis(10),
        "P99 latency too high: {:?}",
        p99
    );
}

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_regime_transitions_performance() {
    // Test GetRegimeTransitions performance (should be <50ms for 100 records)
    let mut client = create_client()
        .await
        .expect("Failed to connect to Trading Service");

    let mut latencies = Vec::new();

    for _ in 0..50 {
        let request = Request::new(GetRegimeTransitionsRequest {
            symbol: "ES.FUT".to_string(),
            limit: 100,
        });

        let start = std::time::Instant::now();
        let _response = client
            .get_regime_transitions(request)
            .await
            .expect("GetRegimeTransitions RPC failed");
        let latency = start.elapsed();

        latencies.push(latency);
    }

    // Calculate statistics
    let avg_latency: std::time::Duration =
        latencies.iter().sum::<std::time::Duration>() / latencies.len() as u32;
    let mut sorted = latencies.clone();
    sorted.sort();
    let p50 = sorted[sorted.len() / 2];
    let p99 = sorted[sorted.len() * 99 / 100];

    println!("✅ GetRegimeTransitions Performance (50 requests, limit=100):");
    println!("   Average: {:?}", avg_latency);
    println!("   P50: {:?}", p50);
    println!("   P99: {:?}", p99);

    // Performance targets: P99 < 50ms
    assert!(
        p99 < std::time::Duration::from_millis(50),
        "P99 latency too high: {:?}",
        p99
    );
}

// ==================== CONCURRENT ACCESS TESTS ====================

#[tokio::test]
#[ignore = "Requires running Trading Service"]
async fn test_concurrent_regime_state_requests() {
    // Test concurrent GetRegimeState requests
    let mut handles = vec![];

    for i in 0..10 {
        let handle = tokio::spawn(async move {
            let mut client = create_client()
                .await
                .expect("Failed to connect to Trading Service");

            let request = Request::new(GetRegimeStateRequest {
                symbol: "ES.FUT".to_string(),
            });

            let response = client
                .get_regime_state(request)
                .await
                .expect(&format!("GetRegimeState failed for request {}", i));

            response.into_inner()
        });

        handles.push(handle);
    }

    // Wait for all requests
    let results: Vec<_> = futures::future::join_all(handles)
        .await
        .into_iter()
        .map(|r| r.expect("Task panicked"))
        .collect();

    // All should succeed
    assert_eq!(results.len(), 10);

    // All should have consistent regime (within a few seconds)
    let regimes: Vec<_> = results.iter().map(|r| r.current_regime.clone()).collect();
    println!("✅ Concurrent requests: regimes={:?}", regimes);
}
