use anyhow::Result;
use clap::{Args, ValueEnum};
use std::path::PathBuf;
use crate::types::Call;
use crate::rpc;
use crate::runner::{self, RunConfig};
use crate::report::generate_html_report;
use super::node_utils::parse_nodes;

#[derive(Debug, Clone, ValueEnum)]
pub enum TestMode { Stress, Spike, Soak }

#[derive(Args)]
pub struct RunArgs {
    #[arg(help = "RPC method to test (e.g., getSlot, getHealth). Run 'aqua ls' for full list")]
    pub test: String,
    #[arg(required = true, help = "RPC endpoints to test (e.g., https://api.mainnet-beta.solana.com or name=url)")]
    pub nodes: Vec<String>,
    #[arg(short = 's', long, help = "Random seed for reproducible test data")]
    pub seed: Option<u64>,
    #[arg(short, long, help = "Suppress verbose output")]
    pub quiet: bool,
    #[arg(short, long, help = "Run differential test comparing responses between nodes")]
    pub equality: bool,
    #[arg(short, long, num_args = 1.., help = "Request rates in rps (e.g., -r 10 50 100)")]
    pub rates: Option<Vec<u64>>,
    #[arg(short, long, help = "Duration in seconds for each rate (default: 10)")]
    pub duration: Option<u64>,
    #[arg(short, long, help = "Output directory for results")]
    pub output: Option<String>,
    #[arg(long, help = "Preview test parameters without executing")]
    pub dry: bool,
    #[arg(long, num_args = 1.., help = "Specific metrics to display")]
    pub metrics: Option<Vec<String>>,
    #[arg(long, help = "Disable figures in output")]
    pub no_figures: bool,
    #[arg(long, help = "Additional arguments to pass to vegeta")]
    pub vegeta_args: Option<String>,
    #[arg(long, default_value = "mainnet-beta", help = "Solana network: mainnet-beta, testnet, devnet")]
    pub network: String,
    #[arg(long, default_value = "10", help = "Number of samples for equality testing")]
    pub equality_samples: usize,
    #[arg(long, help = "Validate RPC responses for errors")]
    pub deep_check: bool,
    #[arg(long, help = "Save raw RPC responses to file")]
    pub save_raw_output: bool,
    #[arg(long, value_enum, help = "Test mode: stress (increasing load), spike (sudden burst), soak (sustained)")]
    pub mode: Option<TestMode>,
    #[arg(long, help = "Generate HTML report with charts")]
    pub html: bool,
}

pub async fn run(args: RunArgs) -> Result<()> {
    let verbose = !args.quiet;
    let nodes = parse_nodes(&args.nodes, verbose).await?;

    let (rates, duration) = match args.mode {
        Some(TestMode::Stress) => (args.rates.unwrap_or(vec![10, 50, 100, 200, 500, 1000]), args.duration.unwrap_or(30)),
        Some(TestMode::Spike) => (args.rates.unwrap_or(vec![10, 1000, 10]), args.duration.unwrap_or(10)),
        Some(TestMode::Soak) => (args.rates.unwrap_or(vec![100]), args.duration.unwrap_or(300)),
        None => (args.rates.unwrap_or(vec![10]), args.duration.unwrap_or(10)),
    };

    if rates.contains(&0) {
        anyhow::bail!("rate must be greater than 0");
    }
    if duration == 0 {
        anyhow::bail!("duration must be greater than 0");
    }

    if args.equality {
        let test = rpc::create_test(&args.test, &[1], Some(args.equality_samples as u64), None, &args.network, args.seed)?;
        let calls: Vec<Call> = test.attacks.iter()
            .flat_map(|a| a.calls.iter())
            .filter_map(|c| serde_json::from_value(c.clone()).ok())
            .take(args.equality_samples)
            .collect();

        if verbose { println!("\nrunning equality test with {} samples\n", calls.len()); }

        let result = runner::run_equality_test(&nodes, &calls, verbose).await?;

        if result.all_equal {
            println!("\nall nodes returned equal results");
        } else {
            println!("\nfound {} differences", result.differences.len());
            for diff in &result.differences {
                println!("  call {}: {} vs {}", diff.call_index, diff.node1, diff.node2);
            }
        }
    } else {
        let result = runner::run(&nodes, RunConfig {
            method: args.test.clone(),
            rates,
            duration: Some(duration),
            durations: None,
            network: args.network,
            seed: args.seed,
            output_dir: args.output,
            verbose,
            dry_run: args.dry,
            deep_check: args.deep_check,
            save_raw_output: args.save_raw_output,
        }).await?;

        if args.html && !args.dry {
            let html = generate_html_report(&result.payload.results, &args.test);
            let html_path = PathBuf::from(&result.output_dir).join("report.html");
            std::fs::write(&html_path, html)?;
            if verbose {
                println!("\nhtml report: {}", html_path.display());
            }
        }
    }

    Ok(())
}
