#![allow(clippy::unnecessary_to_owned)] // String literals in HTML generation

use anyhow::Result;
use clap::{Arg, ArgMatches, Command};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use tokio::fs;
use tracing::{error, info};

// Corrode integration structures (to be implemented)
#[derive(Debug, Clone)]
pub struct CorrodeConfig {
    pub executable_path: String,
    pub workspace_path: String,
    pub timeout_seconds: u64,
    pub max_parallel_sessions: usize,
    pub log_level: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestExecutionRequest {
    pub test_name: String,
    pub test_command: String,
    pub environment: HashMap<String, String>,
    pub working_directory: Option<String>,
    pub timeout_seconds: Option<u64>,
    pub capture_output: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestExecutionResult {
    pub test_name: String,
    pub success: bool,
    pub execution_time: Duration,
    pub exit_code: Option<i32>,
    pub stdout: String,
    pub stderr: String,
    pub error_message: Option<String>,
}

pub struct CorrodeTestRunner {
}

impl CorrodeTestRunner {
    pub fn new(_config: CorrodeConfig) -> Self {
        Self {}
    }

    pub fn with_default_config() -> Self {
        Self::new(CorrodeConfig {
            executable_path: "corrode".to_string(),
            workspace_path: std::env::current_dir()
                .unwrap()
                .to_string_lossy()
                .to_string(),
            timeout_seconds: 600,
            max_parallel_sessions: 4,
            log_level: "info".to_string(),
        })
    }

    pub async fn execute_test(
        &mut self,
        request: TestExecutionRequest,
    ) -> Result<TestExecutionResult> {
        // Stub implementation - would integrate with corrode-mcp
        info!("Executing test: {}", request.test_name);
        Ok(TestExecutionResult {
            test_name: request.test_name,
            success: false,
            execution_time: Duration::from_secs(0),
            exit_code: Some(1),
            stdout: String::new(),
            stderr: "Test runner not fully implemented".to_string(),
            error_message: Some("Corrode integration pending".to_string()),
        })
    }

    pub async fn execute_test_suite(
        &mut self,
        requests: Vec<TestExecutionRequest>,
    ) -> Result<Vec<TestExecutionResult>> {
        let mut results = Vec::new();
        for request in requests {
            results.push(self.execute_test(request).await?);
        }
        Ok(results)
    }

    pub async fn generate_test_report(&self, results: &[TestExecutionResult]) -> Result<String> {
        let total = results.len();
        let passed = results.iter().filter(|r| r.success).count();
        let failed = total - passed;

        let mut report = "# E2E Test Report\n\n".to_string();
        report.push_str(&format!("**Total Tests:** {}\n", total));
        report.push_str(&format!("**Passed:** {}\n", passed));
        report.push_str(&format!("**Failed:** {}\n\n", failed));

        for result in results {
            let status = if result.success { "PASS" } else { "FAIL" };
            report.push_str(&format!(
                "- {} - {} ({:?})\n",
                result.test_name, status, result.execution_time
            ));
        }

        Ok(report)
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    // Setup logging
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    let matches = build_cli().get_matches();

    match matches.subcommand() {
        Some(("run", sub_matches)) => run_tests(sub_matches).await,
        Some(("list", _)) => list_available_tests().await,
        Some(("report", sub_matches)) => generate_report(sub_matches).await,
        Some(("validate", _)) => validate_environment().await,
        _ => {
            eprintln!("Use --help for available commands");
            Ok(())
        },
    }
}

fn build_cli() -> Command {
    Command::new("foxhunt-e2e-runner")
        .version("1.0.0")
        .about("Foxhunt E2E Test Runner with Corrode-MCP Integration")
        .subcommand(
            Command::new("run")
                .about("Run E2E tests")
                .arg(
                    Arg::new("test-pattern")
                        .long("test")
                        .short('t')
                        .value_name("PATTERN")
                        .help("Test pattern to run (e.g., 'trading', 'ml', 'all')")
                        .default_value("all"),
                )
                .arg(
                    Arg::new("parallel")
                        .long("parallel")
                        .short('j')
                        .value_name("COUNT")
                        .help("Number of parallel test sessions")
                        .default_value("4"),
                )
                .arg(
                    Arg::new("timeout")
                        .long("timeout")
                        .value_name("SECONDS")
                        .help("Test timeout in seconds")
                        .default_value("600"),
                )
                .arg(
                    Arg::new("output-dir")
                        .long("output-dir")
                        .short('o')
                        .value_name("DIR")
                        .help("Output directory for test results")
                        .default_value("./test-results"),
                )
                .arg(
                    Arg::new("fail-fast")
                        .long("fail-fast")
                        .action(clap::ArgAction::SetTrue)
                        .help("Stop on first test failure"),
                )
                .arg(
                    Arg::new("verbose")
                        .long("verbose")
                        .short('v')
                        .action(clap::ArgAction::SetTrue)
                        .help("Enable verbose output"),
                ),
        )
        .subcommand(Command::new("list").about("List available E2E tests"))
        .subcommand(
            Command::new("report")
                .about("Generate test report from previous run")
                .arg(
                    Arg::new("results-dir")
                        .long("results-dir")
                        .short('r')
                        .value_name("DIR")
                        .help("Directory containing test results")
                        .default_value("./test-results"),
                )
                .arg(
                    Arg::new("format")
                        .long("format")
                        .short('f')
                        .value_name("FORMAT")
                        .help("Report format (markdown, json, html)")
                        .default_value("markdown"),
                ),
        )
        .subcommand(Command::new("validate").about("Validate test environment and dependencies"))
}

async fn run_tests(matches: &ArgMatches) -> Result<()> {
    let test_pattern = matches.get_one::<String>("test-pattern").unwrap();
    let parallel_count: usize = matches.get_one::<String>("parallel").unwrap().parse()?;
    let timeout: u64 = matches.get_one::<String>("timeout").unwrap().parse()?;
    let output_dir = matches.get_one::<String>("output-dir").unwrap();
    let fail_fast = matches.get_flag("fail-fast");
    let verbose = matches.get_flag("verbose");

    info!("Starting E2E test execution");
    info!("Test pattern: {}", test_pattern);
    info!("Parallel sessions: {}", parallel_count);
    info!("Timeout: {}s", timeout);
    info!("Output directory: {}", output_dir);

    // Create output directory
    fs::create_dir_all(output_dir).await?;

    // Initialize corrode test runner
    let config = CorrodeConfig {
        executable_path: "corrode".to_string(),
        workspace_path: std::env::current_dir()?.to_string_lossy().to_string(),
        timeout_seconds: timeout,
        max_parallel_sessions: parallel_count,
        log_level: if verbose {
            "debug".to_string()
        } else {
            "info".to_string()
        },
    };

    let mut runner = CorrodeTestRunner::new(config);

    // Generate test execution plan
    let test_requests = generate_test_plan(test_pattern, timeout)?;
    info!("Generated {} test requests", test_requests.len());

    // Execute tests
    let start_time = tokio::time::Instant::now();
    let mut results = Vec::new();

    if fail_fast {
        // Execute tests sequentially, stopping on first failure
        for request in test_requests {
            info!("Executing test: {}", request.test_name);
            let result = runner.execute_test(request).await?;

            if verbose {
                println!(
                    "Test: {} - {}",
                    result.test_name,
                    if result.success { "PASSED" } else { "FAILED" }
                );
                if !result.stdout.is_empty() {
                    println!("STDOUT:\n{}", result.stdout);
                }
                if !result.stderr.is_empty() {
                    println!("STDERR:\n{}", result.stderr);
                }
            }

            let success = result.success;
            results.push(result);

            if !success {
                error!("Test failed, stopping execution due to --fail-fast");
                break;
            }
        }
    } else {
        // Execute tests in parallel
        results = runner.execute_test_suite(test_requests).await?;
    }

    let total_duration = start_time.elapsed();

    // Generate and save report
    let report = runner.generate_test_report(&results).await?;
    let report_path = format!("{}/test_report.md", output_dir);
    fs::write(&report_path, &report).await?;

    // Save detailed results as JSON
    let json_results = serde_json::to_string_pretty(&results)?;
    let json_path = format!("{}/test_results.json", output_dir);
    fs::write(&json_path, &json_results).await?;

    // Print summary
    let total_tests = results.len();
    let passed_tests = results.iter().filter(|r| r.success).count();
    let failed_tests = total_tests - passed_tests;

    println!("\n=== E2E Test Summary ===");
    println!("Total Tests: {}", total_tests);
    println!(
        "Passed: {} ({}%)",
        passed_tests,
        (passed_tests as f64 / total_tests as f64 * 100.0) as i32
    );
    println!(
        "Failed: {} ({}%)",
        failed_tests,
        (failed_tests as f64 / total_tests as f64 * 100.0) as i32
    );
    println!("Total Duration: {:?}", total_duration);
    println!("Report saved to: {}", report_path);
    println!("Detailed results: {}", json_path);

    if failed_tests > 0 {
        println!("\nFailed Tests:");
        for result in &results {
            if !result.success {
                println!("  - {} ({:?})", result.test_name, result.execution_time);
                if let Some(error) = &result.error_message {
                    println!("    Error: {}", error);
                }
            }
        }

        std::process::exit(1);
    }

    Ok(())
}

async fn list_available_tests() -> Result<()> {
    println!("Available E2E Test Categories:\n");

    println!("🔧 Service Tests:");
    println!("  - service_startup: Test all services start and respond to health checks");
    println!("  - service_shutdown: Test graceful service shutdown");
    println!("  - service_recovery: Test service recovery after failures\n");

    println!("🗄️  Database Tests:");
    println!("  - database_integration: Test PostgreSQL integration and queries");
    println!("  - database_migrations: Test database schema migrations");
    println!("  - database_performance: Test database query performance\n");

    println!("📡 gRPC Tests:");
    println!("  - grpc_clients: Test all gRPC client connections and authentication");
    println!("  - grpc_streaming: Test streaming gRPC calls");
    println!("  - grpc_error_handling: Test gRPC error scenarios\n");

    println!("🤖 ML Pipeline Tests:");
    println!("  - ml_inference: Test ML model inference pipelines");
    println!("  - ml_training: Test ML model training workflows");
    println!("  - ml_ensemble: Test ensemble prediction workflows\n");

    println!("💼 Trading Tests:");
    println!("  - trading_workflows: Complete trading workflow tests");
    println!("  - order_lifecycle: Order submission to execution lifecycle");
    println!("  - risk_management: Risk management and safety mechanisms");
    println!("  - emergency_stop: Emergency stop and kill switch tests\n");

    println!("🎯 Full Suite:");
    println!("  - all: Run complete E2E test suite");
    println!("  - smoke: Run smoke tests for quick validation");
    println!("  - performance: Run performance and load tests\n");

    println!("Usage Examples:");
    println!("  ./test_runner run --test all              # Run all tests");
    println!("  ./test_runner run --test trading          # Run trading tests only");
    println!("  ./test_runner run --test ml --parallel 2  # Run ML tests with 2 parallel sessions");
    println!(
        "  ./test_runner run --test smoke --fail-fast # Run smoke tests, stop on first failure"
    );

    Ok(())
}

async fn generate_report(matches: &ArgMatches) -> Result<()> {
    let results_dir = matches.get_one::<String>("results-dir").unwrap();
    let format = matches.get_one::<String>("format").unwrap();

    info!("Generating test report from: {}", results_dir);

    let json_path = format!("{}/test_results.json", results_dir);
    let json_content = fs::read_to_string(&json_path)
        .await
        .map_err(|_| anyhow::anyhow!("Could not read test results from {}", json_path))?;

    let results: Vec<TestExecutionResult> = serde_json::from_str(&json_content)?;

    let runner = CorrodeTestRunner::with_default_config();
    let report = runner.generate_test_report(&results).await?;

    match format.as_str() {
        "markdown" | "md" => {
            let output_path = format!("{}/report.md", results_dir);
            fs::write(&output_path, &report).await?;
            println!("Markdown report saved to: {}", output_path);
        },
        "json" => {
            let json_report = serde_json::to_string_pretty(&results)?;
            let output_path = format!("{}/report.json", results_dir);
            fs::write(&output_path, &json_report).await?;
            println!("JSON report saved to: {}", output_path);
        },
        "html" => {
            let html_report = generate_html_report(&results).await?;
            let output_path = format!("{}/report.html", results_dir);
            fs::write(&output_path, &html_report).await?;
            println!("HTML report saved to: {}", output_path);
        },
        _ => {
            return Err(anyhow::anyhow!("Unsupported format: {}", format));
        },
    }

    Ok(())
}

async fn validate_environment() -> Result<()> {
    println!("🔍 Validating E2E Test Environment\n");

    // Check corrode executable
    print!("Checking corrode executable... ");
    match tokio::process::Command::new("corrode")
        .arg("--version")
        .output()
        .await
    {
        Ok(output) => {
            if output.status.success() {
                println!("✅ Found");
                let version = String::from_utf8_lossy(&output.stdout);
                println!("   Version: {}", version.trim());
            } else {
                println!("❌ Error running corrode");
            }
        },
        Err(_) => {
            println!("❌ Not found");
            println!("   Please install corrode-mcp for test execution");
        },
    }

    // Check Rust toolchain
    print!("Checking Rust toolchain... ");
    match tokio::process::Command::new("cargo")
        .arg("--version")
        .output()
        .await
    {
        Ok(output) => {
            if output.status.success() {
                println!("✅ Found");
                let version = String::from_utf8_lossy(&output.stdout);
                println!("   {}", version.trim());
            } else {
                println!("❌ Error running cargo");
            }
        },
        Err(_) => {
            println!("❌ Not found");
        },
    }

    // Check PostgreSQL
    print!("Checking PostgreSQL... ");
    match tokio::process::Command::new("psql")
        .arg("--version")
        .output()
        .await
    {
        Ok(output) => {
            if output.status.success() {
                println!("✅ Found");
                let version = String::from_utf8_lossy(&output.stdout);
                println!("   {}", version.trim());
            } else {
                println!("❌ Error running psql");
            }
        },
        Err(_) => {
            println!("❌ Not found");
            println!("   PostgreSQL is required for database tests");
        },
    }

    // Check environment variables
    println!("\nEnvironment Variables:");
    let required_vars = ["DATABASE_URL", "RUST_LOG"];
    let optional_vars = ["CUDA_VISIBLE_DEVICES", "TORCH_DEVICE"];

    for var in required_vars {
        match std::env::var(var) {
            Ok(value) => println!("  ✅ {}: {}", var, value),
            Err(_) => println!("  ❌ {} (required)", var),
        }
    }

    for var in optional_vars {
        match std::env::var(var) {
            Ok(value) => println!("  ✅ {}: {}", var, value),
            Err(_) => println!("  ➖ {} (optional)", var),
        }
    }

    // Check test compilation
    print!("\nChecking E2E test compilation... ");
    match tokio::process::Command::new("cargo")
        .args(["check", "--package", "e2e_tests"])
        .output()
        .await
    {
        Ok(output) => {
            if output.status.success() {
                println!("✅ Compiles successfully");
            } else {
                println!("❌ Compilation errors");
                let stderr = String::from_utf8_lossy(&output.stderr);
                println!("Errors:\n{}", stderr);
            }
        },
        Err(e) => {
            println!("❌ Error checking compilation: {}", e);
        },
    }

    println!("\n🎯 Environment validation complete!");
    println!("Run './test_runner run --test smoke' to perform a quick smoke test.");

    Ok(())
}

fn generate_test_plan(pattern: &str, timeout: u64) -> Result<Vec<TestExecutionRequest>> {
    let mut requests = Vec::new();

    let base_env = HashMap::from([
        ("RUST_BACKTRACE".to_string(), "1".to_string()),
        ("FOXHUNT_TEST_MODE".to_string(), "true".to_string()),
        (
            "DATABASE_URL".to_string(),
            "postgresql://localhost/foxhunt_test".to_string(),
        ),
    ]);

    match pattern {
        "all" => {
            // Complete test suite
            requests.extend(generate_service_tests(timeout, &base_env));
            requests.extend(generate_database_tests(timeout, &base_env));
            requests.extend(generate_grpc_tests(timeout, &base_env));
            requests.extend(generate_ml_tests(timeout, &base_env));
            requests.extend(generate_trading_tests(timeout, &base_env));
        },
        "smoke" => {
            // Quick validation tests
            requests.push(TestExecutionRequest {
                test_name: "smoke_service_startup".to_string(),
                test_command: "cargo test --package e2e_tests test_service_startup --timeout 30"
                    .to_string(),
                environment: base_env.clone(),
                working_directory: None,
                timeout_seconds: Some(60),
                capture_output: true,
            });
        },
        "services" | "service" => {
            requests.extend(generate_service_tests(timeout, &base_env));
        },
        "database" | "db" => {
            requests.extend(generate_database_tests(timeout, &base_env));
        },
        "grpc" => {
            requests.extend(generate_grpc_tests(timeout, &base_env));
        },
        "ml" => {
            requests.extend(generate_ml_tests(timeout, &base_env));
        },
        "trading" => {
            requests.extend(generate_trading_tests(timeout, &base_env));
        },
        _ => {
            return Err(anyhow::anyhow!("Unknown test pattern: {}", pattern));
        },
    }

    Ok(requests)
}

fn generate_service_tests(
    timeout: u64,
    base_env: &HashMap<String, String>,
) -> Vec<TestExecutionRequest> {
    vec![TestExecutionRequest {
        test_name: "service_startup".to_string(),
        test_command: "cargo test --package e2e_tests test_service_startup".to_string(),
        environment: base_env.clone(),
        working_directory: None,
        timeout_seconds: Some(timeout),
        capture_output: true,
    }]
}

fn generate_database_tests(
    timeout: u64,
    base_env: &HashMap<String, String>,
) -> Vec<TestExecutionRequest> {
    vec![TestExecutionRequest {
        test_name: "database_integration".to_string(),
        test_command: "cargo test --package e2e_tests test_database_integration".to_string(),
        environment: base_env.clone(),
        working_directory: None,
        timeout_seconds: Some(timeout),
        capture_output: true,
    }]
}

fn generate_grpc_tests(
    timeout: u64,
    base_env: &HashMap<String, String>,
) -> Vec<TestExecutionRequest> {
    vec![TestExecutionRequest {
        test_name: "grpc_clients".to_string(),
        test_command: "cargo test --package e2e_tests test_grpc_clients".to_string(),
        environment: base_env.clone(),
        working_directory: None,
        timeout_seconds: Some(timeout),
        capture_output: true,
    }]
}

fn generate_ml_tests(
    timeout: u64,
    base_env: &HashMap<String, String>,
) -> Vec<TestExecutionRequest> {
    vec![TestExecutionRequest {
        test_name: "ml_pipeline".to_string(),
        test_command: "cargo test --package e2e_tests test_ml_pipeline".to_string(),
        environment: base_env.clone(),
        working_directory: None,
        timeout_seconds: Some(timeout),
        capture_output: true,
    }]
}

fn generate_trading_tests(
    timeout: u64,
    base_env: &HashMap<String, String>,
) -> Vec<TestExecutionRequest> {
    vec![TestExecutionRequest {
        test_name: "trading_workflows".to_string(),
        test_command: "cargo test --package e2e_tests test_trading_workflows".to_string(),
        environment: base_env.clone(),
        working_directory: None,
        timeout_seconds: Some(timeout),
        capture_output: true,
    }]
}

async fn generate_html_report(results: &[TestExecutionResult]) -> Result<String> {
    let total_tests = results.len();
    let passed_tests = results.iter().filter(|r| r.success).count();
    let failed_tests = total_tests - passed_tests;

    let mut html = String::new();
    html.push_str("<!DOCTYPE html>\n<html><head><title>Foxhunt E2E Test Report</title>");
    html.push_str("<style>body{font-family:Arial,sans-serif;margin:20px;}");
    html.push_str(".pass{color:green;} .fail{color:red;} .summary{background:#f0f0f0;padding:10px;border-radius:5px;}");
    html.push_str("table{border-collapse:collapse;width:100%;} th,td{border:1px solid #ddd;padding:8px;text-align:left;}");
    html.push_str("</style></head><body>");

    html.push_str(&"<h1>Foxhunt E2E Test Report</h1>".to_string());
    html.push_str(&"<div class='summary'>".to_string());
    html.push_str(&format!(
        "<p>Generated: {}</p>",
        chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
    ));
    html.push_str(&format!("<p>Total Tests: {}</p>", total_tests));
    html.push_str(&format!(
        "<p>Passed: <span class='pass'>{}</span></p>",
        passed_tests
    ));
    html.push_str(&format!(
        "<p>Failed: <span class='fail'>{}</span></p>",
        failed_tests
    ));
    html.push_str(&"</div>".to_string());

    html.push_str("<h2>Test Results</h2><table>");
    html.push_str("<tr><th>Test Name</th><th>Status</th><th>Duration</th><th>Exit Code</th></tr>");

    for result in results {
        let status_class = if result.success { "pass" } else { "fail" };
        let status_text = if result.success { "PASS" } else { "FAIL" };
        let exit_code = result
            .exit_code
            .map(|c| c.to_string())
            .unwrap_or_else(|| "N/A".to_string());

        html.push_str(&format!(
            "<tr><td>{}</td><td class='{}'>{}</td><td>{:?}</td><td>{}</td></tr>",
            result.test_name, status_class, status_text, result.execution_time, exit_code
        ));
    }

    html.push_str("</table></body></html>");

    Ok(html)
}
