use rand::{seq::SliceRandom, Rng};
use serde_json::{json, Value};
use crate::types::{Call, VegetaAttack, LoadTest, TestGenerationParameters};
use crate::VERSION;
use super::samples;

const DEFAULT_DURATION: u64 = 10;
const SLOT_RANGE: (u64, u64) = (394_500_000, 395_000_000);

fn call(method: &str, params: Value) -> Call {
    Call { method: method.to_string(), params }
}

fn rng(seed: Option<u64>) -> impl Rng {
    use rand::SeedableRng;
    seed.map(rand::rngs::StdRng::seed_from_u64)
        .map(|r| Box::new(r) as Box<dyn rand::RngCore>)
        .unwrap_or_else(|| Box::new(rand::thread_rng()))
}

pub fn generate(method: &str, n: usize, network: &str, seed: Option<u64>) -> Vec<Call> {
    match method {
        // Simple methods (no params)
        "getSlot" | "getBlockHeight" | "getHealth" | "getVersion" | "getClusterNodes" |
        "getEpochInfo" | "getEpochSchedule" | "getLeaderSchedule" | "getSupply" |
        "getVoteAccounts" | "getLatestBlockhash" | "getSlotLeader" | "getInflationRate" |
        "getFirstAvailableBlock" | "getGenesisHash" | "getIdentity" | "getHighestSnapshotSlot" |
        "getStakeMinimumDelegation" | "getMaxRetransmitSlot" | "getMaxShredInsertSlot" |
        "getTransactionCount" | "minimumLedgerSlot" =>
            (0..n).map(|_| call(method, json!([]))).collect(),

        // Simple with fixed param
        "getRecentPerformanceSamples" => (0..n).map(|_| call(method, json!([10]))).collect(),
        "getBlockProduction" => (0..n).map(|_| call(method, json!([{"commitment": "finalized"}]))).collect(),
        "getLargestAccounts" => (0..n).map(|_| call(method, json!([{"commitment": "finalized"}]))).collect(),
        "isBlockhashValid" => (0..n).map(|_| call(method, json!(["J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW"]))).collect(),
        "getFeeForMessage" => {
            let msg = "AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA";
            (0..n).map(|_| call(method, json!([msg]))).collect()
        }

        // Slot-based methods
        "getBlock" => random_slots(n, seed).into_iter()
            .map(|s| call(method, json!([s, {"encoding": "json", "transactionDetails": "full", "rewards": false, "maxSupportedTransactionVersion": 0}]))).collect(),
        "getBlockTime" | "getBlockCommitment" => random_slots(n, seed).into_iter()
            .map(|s| call(method, json!([s]))).collect(),
        "getSlotLeaders" => random_slots(n, seed).into_iter()
            .map(|s| call(method, json!([s, 10]))).collect(),
        "getBlocks" => random_slot_ranges(n, 100, seed).into_iter()
            .map(|(s, e)| call(method, json!([s, e]))).collect(),

        // Account-based methods
        "getBalance" => samples::accounts(n, network, seed).into_iter()
            .map(|a| call(method, json!([a]))).collect(),
        "getAccountInfo" => samples::accounts(n, network, seed).into_iter()
            .map(|a| call(method, json!([a, {"encoding": "base64"}]))).collect(),
        "getMultipleAccounts" => samples::accounts(n * 10, network, seed)
            .chunks(10).map(|b| call(method, json!([b, {"encoding": "base64"}]))).collect(),
        "getSignaturesForAddress" => samples::accounts(n, network, seed).into_iter()
            .map(|a| call(method, json!([a, {"limit": 10}]))).collect(),
        "getStakeActivation" => samples::accounts(n, network, seed).into_iter()
            .map(|a| call(method, json!([a]))).collect(),

        // Token methods
        "getTokenAccountBalance" => samples::token_accounts(n, network, seed).into_iter()
            .map(|a| call(method, json!([a]))).collect(),
        "getTokenAccountsByOwner" => samples::accounts(n, network, seed).into_iter()
            .map(|o| call(method, json!([o, {"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"}, {"encoding": "jsonParsed"}]))).collect(),
        "getTokenLargestAccounts" => {
            let mints = ["DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN", "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So"];
            let mut r = rng(seed);
            (0..n).map(|_| call(method, json!([mints.choose(&mut r).unwrap()]))).collect()
        }
        "getTokenSupply" => (0..n).map(|_| call(method, json!(["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"]))).collect(),

        // Transaction methods
        "getTransaction" => samples::transactions(n, network, seed).into_iter()
            .map(|s| call(method, json!([s, {"encoding": "json", "maxSupportedTransactionVersion": 0}]))).collect(),
        "getSignatureStatuses" => {
            let sigs = samples::transactions(n * 5, network, seed);
            sigs.chunks(5).take(n).map(|b| call(method, json!([b, {"searchTransactionHistory": true}]))).collect()
        }
        "simulateTransaction" | "sendTransaction" => {
            let tx = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDArczbMia1tLmq7zz4DinMNN0pJ1JtLdqIJPUw3YrGCzYAMHBsgN27lcgB6H2WQvFgyZuJYHa46puOQo9yQ8CVQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCp20C7Wj2aiuk5TReAXo+VTVg8QTHjs0UjNMMKCvpzZ+ABAgMAAQkDfAAAAAAAAAAA";
            let opts = if method == "simulateTransaction" {
                json!({"encoding": "base64", "commitment": "processed", "replaceRecentBlockhash": true})
            } else {
                json!({"encoding": "base64", "skipPreflight": true, "maxRetries": 0})
            };
            (0..n).map(|_| call(method, json!([tx, opts]))).collect()
        }

        // Program accounts (special handling)
        "getProgramAccounts" => {
            let targets = gpa_targets();
            let mut r = rng(seed);
            let mut t: Vec<_> = targets.iter().collect();
            t.shuffle(&mut r);
            t.into_iter().cycle().take(n).map(|(prog, size, slice)| {
                call(method, json!([prog, {"encoding": "base64", "withContext": true, "dataSlice": {"offset": 0, "length": slice}, "filters": [{"dataSize": size}]}]))
            }).collect()
        }

        // Fee methods
        "getMinimumBalanceForRentExemption" => {
            let mut r = rng(seed);
            (0..n).map(|_| call(method, json!([r.gen_range(0..10000)]))).collect()
        }
        "getRecentPrioritizationFees" | "getInflationReward" => {
            let addrs = samples::accounts(5.min(n), network, seed);
            (0..n).map(|_| call(method, json!([addrs.clone()]))).collect()
        }

        _ => vec![],
    }
}

fn random_slots(n: usize, seed: Option<u64>) -> Vec<u64> {
    let mut r = rng(seed);
    (0..n).map(|_| r.gen_range(SLOT_RANGE.0..SLOT_RANGE.1)).collect()
}

fn random_slot_ranges(n: usize, size: u64, seed: Option<u64>) -> Vec<(u64, u64)> {
    let mut r = rng(seed);
    (0..n).map(|_| {
        let start = r.gen_range(SLOT_RANGE.0..(SLOT_RANGE.1 - size));
        (start, start + size)
    }).collect()
}

fn gpa_targets() -> Vec<(&'static str, u64, u64)> {
    vec![
        ("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", 904, 128),
        ("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", 10136, 128),
        ("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", 653, 128),
        ("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", 1544, 128),
        ("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", 752, 128),
        ("PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", 880, 128),
    ]
}

pub fn available_methods() -> Vec<&'static str> {
    vec![
        "getSlot", "getBlock", "getBlockHeight", "getBlockTime", "getBlocks",
        "getSlotLeader", "getSlotLeaders", "getBlockCommitment", "getBlockProduction",
        "getBalance", "getAccountInfo", "getMultipleAccounts", "getProgramAccounts", "getLargestAccounts",
        "getTokenAccountBalance", "getTokenAccountsByOwner", "getTokenLargestAccounts", "getTokenSupply",
        "getTransaction", "getSignaturesForAddress", "getSignatureStatuses",
        "simulateTransaction", "sendTransaction",
        "getHealth", "getVersion", "getClusterNodes", "getEpochInfo", "getEpochSchedule",
        "getLeaderSchedule", "getLatestBlockhash", "getSupply", "getVoteAccounts",
        "getInflationRate", "getInflationReward", "getRecentPrioritizationFees",
        "getFirstAvailableBlock", "getGenesisHash", "getIdentity", "getHighestSnapshotSlot",
        "getStakeMinimumDelegation", "getMaxRetransmitSlot", "getMaxShredInsertSlot",
        "getTransactionCount", "minimumLedgerSlot",
    ]
}

pub fn create_test(method: &str, rates: &[u64], duration: Option<u64>, durations: Option<&[u64]>, network: &str, seed: Option<u64>) -> anyhow::Result<LoadTest> {
    let normalized = normalize_method(method);
    if !available_methods().contains(&normalized) {
        anyhow::bail!("unknown method: '{}'. Run 'aqua ls' to see available methods", method);
    }
    let method = normalized;

    let durs: Vec<u64> = durations.map(|d| d.to_vec())
        .unwrap_or_else(|| vec![duration.unwrap_or(DEFAULT_DURATION); rates.len()]);
    let total_calls: usize = rates.iter().zip(&durs).map(|(r, d)| (*r as usize) * (*d as usize)).sum();
    let calls = generate(method, total_calls, network, seed);

    let mut attacks = Vec::new();
    let mut idx = 0;
    for (rate, dur) in rates.iter().zip(&durs) {
        let n = (*rate as usize) * (*dur as usize);
        let end = (idx + n).min(calls.len());
        attacks.push(VegetaAttack {
            rate: *rate,
            duration: *dur,
            calls: calls[idx..end].iter().filter_map(|c| serde_json::to_value(c).ok()).collect(),
            vegeta_args: None,
        });
        idx = end;
    }

    Ok(LoadTest {
        test_parameters: TestGenerationParameters {
            aqua_version: VERSION.to_string(),
            test_name: method.to_string(),
            random_seed: seed,
            rates: Some(rates.to_vec()),
            durations: Some(durs),
            vegeta_args: None,
            network: network.to_string(),
        },
        attacks,
    })
}

fn normalize_method(name: &str) -> &'static str {
    match name {
        s if s.starts_with("sol_") => normalize_method(&s[4..]),
        "getSlot" | "slot" => "getSlot",
        "getBlock" | "block" => "getBlock",
        "getBlockHeight" | "blockHeight" => "getBlockHeight",
        "getBlockTime" | "blockTime" => "getBlockTime",
        "getBlocks" | "blocks" => "getBlocks",
        "getSlotLeader" | "slotLeader" => "getSlotLeader",
        "getSlotLeaders" | "slotLeaders" => "getSlotLeaders",
        "getBlockCommitment" | "blockCommitment" => "getBlockCommitment",
        "getBlockProduction" | "blockProduction" => "getBlockProduction",
        "getBalance" | "balance" => "getBalance",
        "getAccountInfo" | "accountInfo" => "getAccountInfo",
        "getMultipleAccounts" | "multipleAccounts" => "getMultipleAccounts",
        "getProgramAccounts" | "programAccounts" => "getProgramAccounts",
        "getLargestAccounts" | "largestAccounts" => "getLargestAccounts",
        "getTokenAccountBalance" | "tokenAccountBalance" => "getTokenAccountBalance",
        "getTokenAccountsByOwner" | "tokenAccountsByOwner" => "getTokenAccountsByOwner",
        "getTokenLargestAccounts" | "tokenLargestAccounts" => "getTokenLargestAccounts",
        "getTokenSupply" | "tokenSupply" => "getTokenSupply",
        "getTransaction" | "transaction" => "getTransaction",
        "getSignaturesForAddress" | "signaturesForAddress" => "getSignaturesForAddress",
        "getSignatureStatuses" | "signatureStatuses" => "getSignatureStatuses",
        "simulateTransaction" => "simulateTransaction",
        "sendTransaction" => "sendTransaction",
        "getHealth" | "health" => "getHealth",
        "getVersion" | "version" => "getVersion",
        "getClusterNodes" | "clusterNodes" => "getClusterNodes",
        "getEpochInfo" | "epochInfo" => "getEpochInfo",
        "getEpochSchedule" | "epochSchedule" => "getEpochSchedule",
        "getLeaderSchedule" | "leaderSchedule" => "getLeaderSchedule",
        "getLatestBlockhash" | "latestBlockhash" => "getLatestBlockhash",
        "getSupply" | "supply" => "getSupply",
        "getVoteAccounts" | "voteAccounts" => "getVoteAccounts",
        "getInflationRate" | "inflationRate" => "getInflationRate",
        "getInflationReward" | "inflationReward" => "getInflationReward",
        "getRecentPrioritizationFees" | "recentPrioritizationFees" => "getRecentPrioritizationFees",
        "getFirstAvailableBlock" | "firstAvailableBlock" => "getFirstAvailableBlock",
        "getGenesisHash" | "genesisHash" => "getGenesisHash",
        "getIdentity" | "identity" => "getIdentity",
        "getHighestSnapshotSlot" | "highestSnapshotSlot" => "getHighestSnapshotSlot",
        "getStakeMinimumDelegation" | "stakeMinimumDelegation" => "getStakeMinimumDelegation",
        "getMaxRetransmitSlot" | "maxRetransmitSlot" => "getMaxRetransmitSlot",
        "getMaxShredInsertSlot" | "maxShredInsertSlot" => "getMaxShredInsertSlot",
        "getTransactionCount" | "transactionCount" => "getTransactionCount",
        "minimumLedgerSlot" => "minimumLedgerSlot",
        _ => "",
    }
}
