#![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::rpc_client::SolanaRpcClient;

/// Build a client with dummy URLs. Uses `new_without_alt_cache` since
/// dummy URLs can't fetch the Pyra ALT.
fn dummy_client(n: usize) -> SolanaRpcClient {
    let urls: Vec<String> = (0..n).map(|i| format!("http://dummy-{i}")).collect();
    SolanaRpcClient::new_without_alt_cache(&urls).expect("failed to create dummy client")
}

// ── constructor ────────────────────────────────────────────

#[test]
fn new_without_alt_cache_rejects_empty_urls() {
    let result = SolanaRpcClient::new_without_alt_cache(&[]);
    assert!(result.is_err());
}

#[test]
fn new_without_alt_cache_accepts_single_url() {
    let result = SolanaRpcClient::new_without_alt_cache(&["http://localhost:8899".to_string()]);
    assert!(result.is_ok());
}

#[test]
fn new_without_alt_cache_accepts_multiple_urls() {
    let urls = vec![
        "http://rpc-1".to_string(),
        "http://rpc-2".to_string(),
        "http://rpc-3".to_string(),
    ];
    let result = SolanaRpcClient::new_without_alt_cache(&urls);
    assert!(result.is_ok());
}

#[test]
fn new_without_alt_cache_has_no_cached_alt() {
    let client = dummy_client(1);
    assert!(client.pyra_address_lookup_table().is_none());
}

// ── shotgun_send (tested via send_transaction-like pattern) ──

// We can't directly call the private shotgun_send, so we test the behaviour
// through the public API. Since the dummy URLs won't connect, we verify
// construction and error handling.

#[test]
fn clone_is_cheap() {
    let client = dummy_client(3);
    let _clone = client.clone();
    // Just verifying Clone works without panic
}

// ── fallback_read (indirect tests via error paths) ──────────

#[tokio::test]
async fn get_latest_blockhash_fails_on_dummy_rpc() {
    let client = dummy_client(1);
    let result = client.get_latest_blockhash().await;
    // Should fail because dummy URL is not a real RPC
    assert!(result.is_err());
}

#[tokio::test]
async fn get_slot_fails_on_dummy_rpc() {
    let client = dummy_client(2);
    let result = client.get_slot().await;
    assert!(result.is_err());
}

#[tokio::test]
async fn get_balance_fails_on_dummy_rpc() {
    let client = dummy_client(1);
    let pubkey = solana_pubkey::Pubkey::new_unique();
    let result = client.get_balance(&pubkey).await;
    assert!(result.is_err());
}

// ── primary_rpc / all_rpcs ────────────────────────────────────

#[test]
fn primary_rpc_returns_first_client() {
    let client = dummy_client(3);
    // Just verify it doesn't panic and returns a reference
    let _rpc = client.primary_rpc();
}

#[test]
fn all_rpcs_returns_correct_count() {
    let client = dummy_client(3);
    assert_eq!(client.all_rpcs().len(), 3);
}

// ── get_minimum_balance_for_rent_exemption ─────────────────────

#[tokio::test]
async fn get_minimum_balance_for_rent_exemption_fails_on_dummy_rpc() {
    let client = dummy_client(1);
    let result = client.get_minimum_balance_for_rent_exemption(165).await;
    assert!(result.is_err());
}

// ── fallback_read (public, direct test) ────────────────────────

#[tokio::test]
async fn fallback_read_is_accessible() {
    use pyra_solana::error::PyraSolanaError;
    let client = dummy_client(1);
    // Use the public fallback_read directly — should fail on dummy RPC
    let result: std::result::Result<u64, PyraSolanaError> = client
        .fallback_read(|rpc| async move {
            rpc.get_slot()
                .await
                .map_err(|e| PyraSolanaError::Rpc(format!("get_slot: {e}")))
        })
        .await;
    assert!(result.is_err());
}

// ── get_token_account_balance ───────────────────────────────────

#[tokio::test]
async fn get_token_account_balance_fails_on_dummy_rpc() {
    let client = dummy_client(1);
    let pubkey = solana_pubkey::Pubkey::new_unique();
    let result = client.get_token_account_balance(&pubkey).await;
    assert!(result.is_err());
}

// ── get_multiple_accounts ────────────────────────────────────────

#[tokio::test]
async fn get_multiple_accounts_fails_on_dummy_rpc() {
    let client = dummy_client(1);
    let pubkeys = vec![
        solana_pubkey::Pubkey::new_unique(),
        solana_pubkey::Pubkey::new_unique(),
    ];
    let result = client.get_multiple_accounts(&pubkeys).await;
    assert!(result.is_err());
}

// ── confirm_transaction ──────────────────────────────────────────

#[tokio::test]
async fn confirm_transaction_times_out_on_dummy_rpc() {
    let client = dummy_client(1);
    let sig = solana_signature::Signature::from([1u8; 64]);
    let result = client
        .confirm_transaction(&sig, false, std::time::Duration::from_millis(100))
        .await;
    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    // Should fail due to RPC error (not timeout, since get_signature_statuses fails first)
    assert!(err.contains("get_signature_statuses") || err.contains("not confirmed"));
}

// ── get_token_accounts_by_owner ──────────────────────────────────

#[tokio::test]
async fn get_token_accounts_by_owner_fails_on_dummy_rpc() {
    use pyra_solana::TokenAccountsFilter;
    let client = dummy_client(1);
    let owner = solana_pubkey::Pubkey::new_unique();
    let mint = solana_pubkey::Pubkey::new_unique();
    let result = client
        .get_token_accounts_by_owner(&owner, TokenAccountsFilter::Mint(mint))
        .await;
    assert!(result.is_err());
}
