use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyDict};
use pyo3_asyncio::tokio::future_into_py;
use solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig};
use solana_sdk::{
    commitment_config::CommitmentConfig,
    hash::Hash,
    instruction::{AccountMeta, Instruction},
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    transaction::Transaction,
};
use spl_associated_token_account::{get_associated_token_address, instruction::create_associated_token_account};
use std::{
    collections::HashMap,
    str::FromStr,
    sync::{atomic::{AtomicU64, Ordering}, Arc},
    time::{SystemTime, UNIX_EPOCH},
};
use tokio::sync::Mutex;

const RAYDIUM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const SYSTEM_PROGRAM_ID: &str = "11111111111111111111111111111111";

#[pyclass]
#[derive(Clone)]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    cached_blockhash: Arc<Mutex<Option<Hash>>>,
    last_refresh: Arc<AtomicU64>,
}

#[pyclass]
#[derive(Clone)]
pub struct TradeParams {
    #[pyo3(get, set)]
    pub accounts: HashMap<String, String>,
    #[pyo3(get, set)]
    pub amount_in: f64,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub victim_data: Option<Vec<u8>>,
    #[pyo3(get, set)]
    pub reuse_blockhash: bool,
    #[pyo3(get, set)]
    pub spl_token_mint: String,
}

impl TradingCore {
    async fn get_blockhash(&self) -> PyResult<Hash> {
        let mut cached = self.cached_blockhash.lock().await;
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        if let Some(hash) = *cached {
            if now - self.last_refresh.load(Ordering::Relaxed) < 60 {
                return Ok(hash);
            }
        }

        for _ in 0..3 {
            match self.client.get_latest_blockhash().await {
                Ok(new_blockhash) => {
                    *cached = Some(new_blockhash);
                    self.last_refresh.store(now, Ordering::Relaxed);
                    return Ok(new_blockhash);
                }
                Err(e) => {
                    if e.to_string().contains("429") {
                        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
                        continue;
                    }
                    return Err(PyRuntimeError::new_err(format!("Blockhash error: {}", e)));
                }
            }
        }

        Err(PyRuntimeError::new_err("Failed to get blockhash after retries"))
    }

    async fn create_token_account(&self, token_mint: &Pubkey) -> PyResult<()> {
        let wallet = self.keypair.pubkey();
        let token_ata = get_associated_token_address(&wallet, token_mint);

        // Check if account exists
        if self.client.get_account(&token_ata).await.is_ok() {
            return Ok(());
        }

        // Create ATA instruction
        let create_ata_ix = create_associated_token_account(
            &wallet,
            &wallet,
            token_mint,
            &Pubkey::from_str(TOKEN_PROGRAM_ID).unwrap(),
        );

        // Build and send transaction
        let blockhash = self.get_blockhash().await?;
        let message = Message::new(&[create_ata_ix], Some(&wallet));
        let mut tx = Transaction::new_unsigned(message);
        tx.sign(&[&*self.keypair], blockhash);

        let config = RpcSendTransactionConfig {
            skip_preflight: true,
            preflight_commitment: None,
            encoding: None,
            max_retries: Some(3),
            min_context_slot: None,
        };

        self.client.send_transaction_with_config(&tx, config).await
            .map_err(|e| PyRuntimeError::new_err(format!("ATA creation failed: {}", e)))?;

        // Wait for confirmation
        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        Ok(())
    }
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(rpc_url: String, keypair_bytes: Vec<u8>) -> PyResult<Self> {
        let client = Arc::new(RpcClient::new_with_commitment(
            rpc_url,
            CommitmentConfig::confirmed(),
        ));

        let keypair = Arc::new(
            Keypair::from_bytes(&keypair_bytes)
                .map_err(|e| PyRuntimeError::new_err(format!("Invalid keypair: {}", e)))?,
        );

        Ok(Self {
            client,
            keypair,
            cached_blockhash: Arc::new(Mutex::new(None)),
            last_refresh: Arc::new(AtomicU64::new(0)),
        })
    }

    fn execute_trade_py<'py>(
        &self,
        py: Python<'py>,
        params: &TradeParams,
    ) -> PyResult<&'py PyAny> {
        let self_clone = self.clone();
        let params = params.clone();

        future_into_py(py, async move {
            // Parse token mint
            let token_mint = Pubkey::from_str(&params.spl_token_mint)
                .map_err(|e| PyRuntimeError::new_err(format!("Invalid token mint: {}", e)))?;

            // Get wallet pubkey
            let wallet = self_clone.keypair.pubkey();

            // Helper function to get account pubkey
            let get_account = |key: &str| -> PyResult<Pubkey> {
                params.accounts
                    .get(key)
                    .ok_or_else(|| PyRuntimeError::new_err(format!("Missing account: {}", key)))
                    .and_then(|addr| Pubkey::from_str(addr)
                        .map_err(|e| PyRuntimeError::new_err(format!("Invalid account {}: {}", key, e))))
            };

            // Ensure ATA exists
            let token_ata = get_associated_token_address(&wallet, &token_mint);
            self_clone.create_token_account(&token_mint).await?;

            // Build Raydium swap instruction
            let amount_lamports = (params.amount_in * 1_000_000_000.0) as u64;
            let mut instruction_data = vec![
                if params.is_buy { 0x03 } else { 0x04 },
            ];
            instruction_data.extend_from_slice(&amount_lamports.to_le_bytes());

            // Use victim data if provided, otherwise use our instruction data
            let data = params.victim_data.unwrap_or(instruction_data);

            // Build accounts vector
            let accounts = vec![
                AccountMeta::new(wallet, true),
                AccountMeta::new(token_ata, false),
                AccountMeta::new(get_account("wsol_account")?, false),
                AccountMeta::new(get_account("market")?, false),
                AccountMeta::new_readonly(get_account("amm_authority")?, false),
                AccountMeta::new(get_account("amm_open_orders")?, false),
                AccountMeta::new(get_account("amm_target")?, false),
                AccountMeta::new(get_account("serum_market")?, false),
                AccountMeta::new(get_account("serum_bids")?, false),
                AccountMeta::new(get_account("serum_asks")?, false),
                AccountMeta::new(get_account("event_queue")?, false),
                AccountMeta::new(get_account("base_vault")?, false),
                AccountMeta::new(get_account("quote_vault")?, false),
                AccountMeta::new_readonly(get_account("vault_signer")?, false),
                AccountMeta::new_readonly(Pubkey::from_str(TOKEN_PROGRAM_ID).unwrap(), false),
                AccountMeta::new_readonly(Pubkey::from_str(SYSTEM_PROGRAM_ID).unwrap(), false),
            ];

            // Create instruction
            let raydium_program = Pubkey::from_str(RAYDIUM_PROGRAM_ID)
                .map_err(|e| PyRuntimeError::new_err(format!("Invalid Raydium program ID: {}", e)))?;

            let swap_ix = Instruction {
                program_id: raydium_program,
                accounts,
                data,
            };

            // Build and send transaction
            let message = Message::new(&[swap_ix], Some(&wallet));
            let blockhash = self_clone.get_blockhash().await?;
            let mut tx = Transaction::new_unsigned(message);
            tx.sign(&[&*self_clone.keypair], blockhash);

            let config = RpcSendTransactionConfig {
                skip_preflight: true,
                preflight_commitment: None,
                encoding: None,
                max_retries: Some(3),
                min_context_slot: None,
            };

            // Send with retries
            let mut retries = 3;
            loop {
                match self_clone.client.send_transaction_with_config(&tx, config.clone()).await {
                    Ok(signature) => return Ok(signature.to_string()),
                    Err(_) if retries > 0 => {
                        retries -= 1;
                        tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
                    }
                    Err(e) => return Err(PyRuntimeError::new_err(format!("Transaction failed: {}", e))),
                }
            }
        })
    }
}

#[pymethods]
impl TradeParams {
    #[new]
    #[pyo3(signature = (accounts, amount_in, is_buy, spl_token_mint, victim_data=None, reuse_blockhash=false))]
    fn new(
        accounts: &PyDict,
        amount_in: f64,
        is_buy: bool,
        spl_token_mint: String,
        victim_data: Option<Vec<u8>>,
        reuse_blockhash: bool,
    ) -> PyResult<Self> {
        let mut accounts_map = HashMap::new();
        for (key, value) in accounts {
            accounts_map.insert(
                key.extract::<String>()?,
                value.extract::<String>()?,
            );
        }
        Ok(Self {
            accounts: accounts_map,
            amount_in,
            is_buy,
            victim_data,
            reuse_blockhash,
            spl_token_mint,
        })
    }
}

#[pymodule]
fn solana_rust_bot(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<TradingCore>()?;
    m.add_class::<TradeParams>()?;
    Ok(())
}
 this lib.rs or the 2nd one use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyDict};
use pyo3_asyncio::tokio::future_into_py;
use solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig};
use solana_sdk::{
    commitment_config::CommitmentConfig,
    hash::Hash,
    instruction::{AccountMeta, Instruction},
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    transaction::Transaction,
};
use spl_associated_token_account::{get_associated_token_address, instruction::create_associated_token_account};
use std::{
    collections::HashMap,
    str::FromStr,
    sync::{atomic::{AtomicU64, Ordering}, Arc},
    time::{SystemTime, UNIX_EPOCH},
};
use tokio::sync::Mutex;

const RAYDIUM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const SYSTEM_PROGRAM_ID: &str = "11111111111111111111111111111111";

#[pyclass]
#[derive(Clone)]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    cached_blockhash: Arc<Mutex<Option<Hash>>>,
    last_refresh: Arc<AtomicU64>,
}

#[pyclass]
#[derive(Clone)]
pub struct TradeParams {
    #[pyo3(get, set)]
    pub accounts: HashMap<String, String>,
    #[pyo3(get, set)]
    pub amount_in: f64,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub victim_data: Option<Vec<u8>>,
    #[pyo3(get, set)]
    pub reuse_blockhash: bool,
    #[pyo3(get, set)]
    pub spl_token_mint: String,
}

impl TradingCore {
    async fn get_blockhash(&self) -> PyResult<Hash> {
        let mut cached = self.cached_blockhash.lock().await;
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        
        if let Some(hash) = *cached {
            if now - self.last_refresh.load(Ordering::Relaxed) < 60 {
                return Ok(hash);
            }
        }
        
        for _ in 0..3 {
            match self.client.get_latest_blockhash().await {
                Ok(new_blockhash) => {
                    *cached = Some(new_blockhash);
                    self.last_refresh.store(now, Ordering::Relaxed);
                    return Ok(new_blockhash);
                }
                Err(e) => {
                    if e.to_string().contains("429") {
                        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
                        continue;
                    }
                    return Err(PyRuntimeError::new_err(format!("Blockhash error: {}", e)));
                }
            }
        }
        
        Err(PyRuntimeError::new_err("Failed to get blockhash after retries"))
    }

    async fn create_token_account(&self, token_mint: &Pubkey) -> PyResult<()> {
        let wallet = self.keypair.pubkey();
        let token_ata = get_associated_token_address(&wallet, token_mint);

        // Check if account exists
        if self.client.get_account(&token_ata).await.is_ok() {
            return Ok(());
        }

        // Create ATA instruction
        let create_ata_ix = create_associated_token_account(
            &wallet,
            &wallet,
            token_mint,
            &Pubkey::from_str(TOKEN_PROGRAM_ID).unwrap(),
        );

        // Build and send transaction
        let blockhash = self.get_blockhash().await?;
        let message = Message::new(&[create_ata_ix], Some(&wallet));
        let mut tx = Transaction::new_unsigned(message);
        tx.sign(&[&*self.keypair], blockhash);

        let config = RpcSendTransactionConfig {
            skip_preflight: true,
            preflight_commitment: None,
            encoding: None,
            max_retries: Some(3),
            min_context_slot: None,
        };

        self.client.send_transaction_with_config(&tx, config).await
            .map_err(|e| PyRuntimeError::new_err(format!("ATA creation failed: {}", e)))?;

        // Wait for confirmation
        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        Ok(())
    }
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(rpc_url: String, keypair_bytes: Vec<u8>) -> PyResult<Self> {
        let client = Arc::new(RpcClient::new_with_commitment(
            rpc_url,
            CommitmentConfig::confirmed(),
        ));

        let keypair = Arc::new(
            Keypair::from_bytes(&keypair_bytes)
                .map_err(|e| PyRuntimeError::new_err(format!("Invalid keypair: {}", e)))?,
        );

        Ok(Self {
            client,
            keypair,
            cached_blockhash: Arc::new(Mutex::new(None)),
            last_refresh: Arc::new(AtomicU64::new(0)),
        })
    }

    fn execute_trade_py<'py>(
        &self,
        py: Python<'py>,
        params: &TradeParams,
    ) -> PyResult<&'py PyAny> {
        let self_clone = self.clone();
        let params = params.clone();

        future_into_py(py, async move {
            // Parse token mint
            let token_mint = Pubkey::from_str(&params.spl_token_mint)
                .map_err(|e| PyRuntimeError::new_err(format!("Invalid token mint: {}", e)))?;
            
            // Get wallet pubkey
            let wallet = self_clone.keypair.pubkey();
            
            // Helper function to get account pubkey
            let get_account = |key: &str| -> PyResult<Pubkey> {
                params.accounts
                    .get(key)
                    .ok_or_else(|| PyRuntimeError::new_err(format!("Missing account: {}", key)))
                    .and_then(|addr| Pubkey::from_str(addr)
                        .map_err(|e| PyRuntimeError::new_err(format!("Invalid account {}: {}", key, e))))
            };

            // Ensure ATA exists
            let token_ata = get_associated_token_address(&wallet, &token_mint);
            self_clone.create_token_account(&token_mint).await?;

            // Build Raydium swap instruction
            let amount_lamports = (params.amount_in * 1_000_000_000.0) as u64;
            let mut instruction_data = vec![
                if params.is_buy { 0x03 } else { 0x04 },
            ];
            instruction_data.extend_from_slice(&amount_lamports.to_le_bytes());
            
            // Use victim data if provided, otherwise use our instruction data
            let data = params.victim_data.unwrap_or(instruction_data);

            // Build accounts vector
            let accounts = vec![
                AccountMeta::new(wallet, true),
                AccountMeta::new(token_ata, false),
                AccountMeta::new(get_account("wsol_account")?, false),
                AccountMeta::new(get_account("market")?, false),
                AccountMeta::new_readonly(get_account("amm_authority")?, false),
                AccountMeta::new(get_account("amm_open_orders")?, false),
                AccountMeta::new(get_account("amm_target")?, false),
                AccountMeta::new(get_account("serum_market")?, false),
                AccountMeta::new(get_account("serum_bids")?, false),
                AccountMeta::new(get_account("serum_asks")?, false),
                AccountMeta::new(get_account("event_queue")?, false),
                AccountMeta::new(get_account("base_vault")?, false),
                AccountMeta::new(get_account("quote_vault")?, false),
                AccountMeta::new_readonly(get_account("vault_signer")?, false),
                AccountMeta::new_readonly(Pubkey::from_str(TOKEN_PROGRAM_ID).unwrap(), false),
                AccountMeta::new_readonly(Pubkey::from_str(SYSTEM_PROGRAM_ID).unwrap(), false),
            ];

            // Create instruction
            let raydium_program = Pubkey::from_str(RAYDIUM_PROGRAM_ID)
                .map_err(|e| PyRuntimeError::new_err(format!("Invalid Raydium program ID: {}", e)))?;
            
            let swap_ix = Instruction {
                program_id: raydium_program,
                accounts,
                data,
            };

            // Build and send transaction
            let message = Message::new(&[swap_ix], Some(&wallet));
            let blockhash = self_clone.get_blockhash().await?;
            let mut tx = Transaction::new_unsigned(message);
            tx.sign(&[&*self_clone.keypair], blockhash);

            let config = RpcSendTransactionConfig {
                skip_preflight: true,
                preflight_commitment: None,
                encoding: None,
                max_retries: Some(3),
                min_context_slot: None,
            };

            // Send with retries
            let mut retries = 3;
            loop {
                match self_clone.client.send_transaction_with_config(&tx, config.clone()).await {
                    Ok(signature) => return Ok(signature.to_string()),
                    Err(_) if retries > 0 => {
                        retries -= 1;
                        tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
                    }
                    Err(e) => return Err(PyRuntimeError::new_err(format!("Transaction failed: {}", e))),
                }
            }
        })
    }
}

#[pymethods]
impl TradeParams {
    #[new]
    #[pyo3(signature = (accounts, amount_in, is_buy, spl_token_mint, victim_data=None, reuse_blockhash=false))]
    fn new(
        accounts: &PyDict,
        amount_in: f64,
        is_buy: bool,
        spl_token_mint: String,
        victim_data: Option<Vec<u8>>,
        reuse_blockhash: bool,
    ) -> PyResult<Self> {
        let mut accounts_map = HashMap::new();
        for (key, value) in accounts {
            accounts_map.insert(
                key.extract::<String>()?,
                value.extract::<String>()?,
            );
        }
        Ok(Self {
            accounts: accounts_map,
            amount_in,
            is_buy,
            victim_data,
            reuse_blockhash,
            spl_token_mint,
        })
    }
}

#[pymodule]
fn solana_rust_bot(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<TradingCore>()?;
    m.add_class::<TradeParams>()?;
    Ok(())
}