//! End-to-End Tests for `tli train list` Command
//!
//! Tests the complete flow: TLI CLI → API Gateway → ML Training Service

use fxt::proto::ml_training::{
    ml_training_service_server::MlTrainingServiceServer, TrainingStatus,
};
use tonic::transport::Server;
use std::net::SocketAddr;
use tokio::time::{timeout, Duration};

use super::mock_ml_training_service::{MockMlTrainingService, MockTrainingState};
use super::test_fixtures::{self, create_test_job_batch, filter_by_status, filter_by_model, create_test_job, extract_job_ids, validate_job_structure};

/// Helper to start mock gRPC server
async fn start_mock_server(state: MockTrainingState) -> SocketAddr {
    let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
    let service = MockMlTrainingService::new(state);

    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    let local_addr = listener.local_addr().unwrap();

    tokio::spawn(async move {
        Server::builder()
            .add_service(MlTrainingServiceServer::new(service))
            .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
            .await
            .unwrap();
    });

    // Give server time to start
    tokio::time::sleep(Duration::from_millis(100)).await;
    local_addr
}

/// TEST 1: List all jobs (no filter)
#[tokio::test]
async fn test_list_all_jobs() {
    let state = MockTrainingState::default();

    // Add test jobs
    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
    }

    let _addr = start_mock_server(state.clone()).await;

    // Verify all jobs are in state
    let all_jobs = state.list_jobs();
    assert_eq!(all_jobs.len(), 5, "Should have 5 jobs");
}

/// TEST 2: Filter by status (RUNNING)
#[tokio::test]
async fn test_filter_by_status_running() {
    let state = MockTrainingState::default();

    // Add test jobs
    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
    }

    let _addr = start_mock_server(state.clone()).await;

    // Filter running jobs
    let all_jobs = state.list_jobs();
    let running_jobs = filter_by_status(&all_jobs, TrainingStatus::Running);
    assert_eq!(running_jobs.len(), 1, "Should have 1 running job");
    assert_eq!(running_jobs[0].model_type, "TFT");
}

/// TEST 3: Filter by status (COMPLETED)
#[tokio::test]
async fn test_filter_by_status_completed() {
    let state = MockTrainingState::default();

    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
    }

    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    let completed_jobs = filter_by_status(&all_jobs, TrainingStatus::Completed);
    assert_eq!(completed_jobs.len(), 2, "Should have 2 completed jobs");
}

/// TEST 4: Filter by model type (TFT)
#[tokio::test]
async fn test_filter_by_model_tft() {
    let state = MockTrainingState::default();

    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
    }

    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    let tft_jobs = filter_by_model(&all_jobs, "TFT");
    assert_eq!(tft_jobs.len(), 2, "Should have 2 TFT jobs");
}

/// TEST 5: Filter by model type (DQN)
#[tokio::test]
async fn test_filter_by_model_dqn() {
    let state = MockTrainingState::default();

    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
    }

    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    let dqn_jobs = filter_by_model(&all_jobs, "DQN");
    assert_eq!(dqn_jobs.len(), 1, "Should have 1 DQN job");
    assert_eq!(dqn_jobs[0].job_id, "train_dqn_nq_20251022_133000");
}

/// TEST 6: Pagination - first page
#[tokio::test]
async fn test_pagination_first_page() {
    let state = MockTrainingState::default();

    // Add 10 jobs
    for i in 0..10 {
        let job = create_test_job(
            &format!("job_{}", i),
            "TFT",
            TrainingStatus::Completed,
        );
        state.add_job(job);
    }

    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    assert_eq!(all_jobs.len(), 10);
}

/// TEST 7: Empty job list
#[tokio::test]
async fn test_empty_job_list() {
    let state = MockTrainingState::default();
    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    assert_eq!(all_jobs.len(), 0, "Should have no jobs");
}

/// TEST 8: Combined filters (status + model)
#[tokio::test]
async fn test_combined_filters() {
    let state = MockTrainingState::default();

    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
    }

    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    let completed_tft = all_jobs
        .iter()
        .filter(|j| j.status == TrainingStatus::Completed as i32 && j.model_type == "TFT")
        .count();

    // Only stopped TFT job in our batch
    assert_eq!(completed_tft, 0, "No completed TFT jobs in test batch");
}

/// TEST 9: Job ID extraction
#[tokio::test]
async fn test_job_id_extraction() {
    let state = MockTrainingState::default();

    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
    }

    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    let job_ids = extract_job_ids(&all_jobs);
    assert_eq!(job_ids.len(), 5);
    assert!(job_ids.contains(&"train_tft_es_20251022_140000".to_string()));
}

/// TEST 10: Job structure validation
#[tokio::test]
async fn test_job_structure_validation() {
    let state = MockTrainingState::default();

    let jobs = create_test_job_batch();
    for job in &jobs {
        state.add_job(job.clone());
        assert!(validate_job_structure(job), "Job structure should be valid");
    }

    let _addr = start_mock_server(state.clone()).await;

    let all_jobs = state.list_jobs();
    for job in &all_jobs {
        assert!(validate_job_structure(job), "All jobs should have valid structure");
    }
}
