#![allow(
    clippy::allow_attributes,
    clippy::allow_attributes_without_reason,
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::arithmetic_side_effects,
    reason = "test code"
)]

use pyra_solana::tx_builder::{
    build_legacy_transaction_raw, build_v0_transaction_raw, partial_sign_transaction,
    sign_transaction,
};
use solana_hash::Hash;
use solana_instruction::{AccountMeta, Instruction};
use solana_keypair::Keypair;
use solana_pubkey::Pubkey;
use solana_signature::Signature;
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;

fn dummy_instruction(payer: &Pubkey) -> Instruction {
    Instruction {
        program_id: Pubkey::new_unique(),
        accounts: vec![AccountMeta::new(*payer, true)],
        data: vec![0, 1, 2, 3],
    }
}

fn dummy_instructions(payer: &Pubkey) -> Vec<Instruction> {
    vec![dummy_instruction(payer)]
}

// --- build_v0_transaction ---

#[test]
fn build_v0_transaction_produces_correct_signature_count() {
    let payer = Pubkey::new_unique();
    let ix = dummy_instructions(&payer);
    let blockhash = Hash::new_unique();

    let tx = build_v0_transaction_raw(&ix, &payer, &[], blockhash).unwrap();

    assert_eq!(tx.signatures.len(), 1, "single signer = 1 signature slot");
    assert_eq!(tx.signatures[0], Signature::default(), "should be unsigned");
}

#[test]
fn build_v0_transaction_multiple_signers() {
    let payer = Pubkey::new_unique();
    let other_signer = Pubkey::new_unique();
    let ix = Instruction {
        program_id: Pubkey::new_unique(),
        accounts: vec![
            AccountMeta::new(payer, true),
            AccountMeta::new(other_signer, true),
        ],
        data: vec![],
    };
    let blockhash = Hash::new_unique();

    let tx = build_v0_transaction_raw(&[ix], &payer, &[], blockhash).unwrap();

    assert_eq!(tx.signatures.len(), 2, "two signers = 2 signature slots");
}

// --- build_legacy_transaction ---

#[test]
fn build_legacy_transaction_produces_correct_signature_count() {
    let payer = Pubkey::new_unique();
    let ix = dummy_instructions(&payer);
    let blockhash = Hash::new_unique();

    let tx = build_legacy_transaction_raw(&ix, &payer, blockhash).unwrap();

    assert_eq!(tx.signatures.len(), 1);
    assert_eq!(tx.signatures[0], Signature::default());
}

// --- sign_transaction ---

#[test]
fn sign_transaction_produces_valid_signature() {
    let keypair = Keypair::new();
    let payer = keypair.pubkey();
    let ix = dummy_instructions(&payer);
    let blockhash = Hash::new_unique();

    let unsigned = build_v0_transaction_raw(&ix, &payer, &[], blockhash).unwrap();
    let signed = sign_transaction(unsigned.message, &[&keypair]).unwrap();

    assert_ne!(signed.signatures[0], Signature::default());
}

// --- partial_sign_transaction ---

#[test]
fn partial_sign_transaction_signs_first_slot() {
    let keypair = Keypair::new();
    let payer = keypair.pubkey();
    let ix = dummy_instructions(&payer);
    let blockhash = Hash::new_unique();

    let mut tx = build_v0_transaction_raw(&ix, &payer, &[], blockhash).unwrap();
    assert_eq!(tx.signatures[0], Signature::default());

    partial_sign_transaction(&mut tx, &keypair).unwrap();

    assert_ne!(tx.signatures[0], Signature::default());
}

#[test]
fn partial_sign_transaction_fails_on_empty_signatures() {
    let keypair = Keypair::new();
    let mut tx = VersionedTransaction {
        signatures: vec![],
        message: {
            let payer = keypair.pubkey();
            let ix = dummy_instructions(&payer);
            let blockhash = Hash::new_unique();
            build_v0_transaction_raw(&ix, &payer, &[], blockhash)
                .unwrap()
                .message
        },
    };

    let result = partial_sign_transaction(&mut tx, &keypair);
    assert!(result.is_err(), "should fail with no signature slots");
}

// --- IQR filtering ---

use pyra_solana::tx_builder::{
    estimate_transaction_fee, estimate_transaction_fee_with_gas_limit, iqr_filtered_mean,
};

#[test]
fn iqr_filtered_mean_empty() {
    let mut values = vec![];
    assert_eq!(iqr_filtered_mean(&mut values).unwrap(), 0);
}

#[test]
fn iqr_filtered_mean_single_element() {
    let mut values = vec![100];
    assert_eq!(iqr_filtered_mean(&mut values).unwrap(), 100);
}

#[test]
fn iqr_filtered_mean_uniform_values() {
    let mut values = vec![50, 50, 50, 50];
    assert_eq!(iqr_filtered_mean(&mut values).unwrap(), 50);
}

#[test]
fn iqr_filtered_mean_filters_outliers() {
    // Values with a clear outlier
    let mut values = vec![10, 11, 12, 13, 14, 15, 100_000];
    let result = iqr_filtered_mean(&mut values).unwrap();
    // The outlier (100_000) should be filtered out, mean should be ~12
    assert!(result <= 15, "outlier should be filtered, got {result}");
    assert!(result >= 10, "mean should be in range, got {result}");
}

#[test]
fn iqr_filtered_mean_all_same_returns_that_value() {
    let mut values = vec![42, 42, 42, 42, 42, 42, 42, 42];
    assert_eq!(iqr_filtered_mean(&mut values).unwrap(), 42);
}

#[test]
fn iqr_filtered_mean_two_elements() {
    let mut values = vec![10, 20];
    let result = iqr_filtered_mean(&mut values).unwrap();
    assert_eq!(result, 15); // (10+20)/2
}

// --- Gas guard ---

#[test]
fn estimate_transaction_fee_basic() {
    // 400_000 CU * 1_000 microlamports / 1_000_000 = 400 lamports + 5_000 base = 5_400
    assert_eq!(estimate_transaction_fee(400_000, 1_000), 5_400);
}

#[test]
fn estimate_transaction_fee_zero_priority() {
    // 0 priority fee = just the base fee
    assert_eq!(estimate_transaction_fee(400_000, 0), 5_000);
}

#[test]
fn estimate_transaction_fee_with_gas_limit_within_budget() {
    let result = estimate_transaction_fee_with_gas_limit(400_000, 1_000, 10_000);
    assert!(result.is_ok());
    assert_eq!(result.unwrap(), 5_400);
}

#[test]
fn estimate_transaction_fee_with_gas_limit_exceeds_budget() {
    let result = estimate_transaction_fee_with_gas_limit(400_000, 1_000, 1_000);
    assert!(result.is_err());
}

#[test]
fn estimate_transaction_fee_with_gas_limit_exact_boundary() {
    // Estimated = 5_400, max = 5_400 — should pass
    let result = estimate_transaction_fee_with_gas_limit(400_000, 1_000, 5_400);
    assert!(result.is_ok());
}

// --- Legacy transaction round-trip ---

#[test]
fn legacy_sign_round_trip() {
    let keypair = Keypair::new();
    let payer = keypair.pubkey();
    let ix = dummy_instructions(&payer);
    let blockhash = Hash::new_unique();

    let unsigned = build_legacy_transaction_raw(&ix, &payer, blockhash).unwrap();
    let signed = sign_transaction(unsigned.message, &[&keypair]).unwrap();

    assert_ne!(signed.signatures[0], Signature::default());
    assert_eq!(signed.signatures.len(), 1);
}
