use log::{info, warn};
use pyo3::{exceptions::PyValueError, prelude::*};
use pyo3_asyncio::tokio::future_into_py;
use serde::{Deserialize, Serialize};
use solana_client::{
    nonblocking::rpc_client::RpcClient,
    rpc_config::RpcSendTransactionConfig,
};
use solana_sdk::{
    commitment_config::{CommitmentConfig, CommitmentLevel},
    compute_budget::ComputeBudgetInstruction,
    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_idempotent,
};
use spl_token;
use std::{str::FromStr, sync::Arc};
use tokio::sync::RwLock;
use std::collections::HashMap;

// Constants
const COMPUTE_UNITS: u32 = 50_000;
const PRIORITY_FEE: u64 = 750_000;
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;

#[derive(Debug, Serialize, Deserialize, Clone)]
struct BalanceState {
    sol: f64,
    wsol: f64,
    updated_at: i64,
}

#[pyclass]
#[derive(Clone)]
pub struct TradeParams {
    #[pyo3(get, set)]
    pub accounts: Vec<String>,
    #[pyo3(get, set)]
    pub amount_in: Option<u64>,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub token_a_mint: String,
    #[pyo3(get, set)]
    pub token_b_mint: String,
    #[pyo3(get, set)]
    pub victim_data: Option<Vec<u8>>,
    #[pyo3(get, set)]
    pub sell_all: bool,
}

#[pyclass]
#[derive(Clone)]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    ata_cache: Arc<RwLock<HashMap<String, Pubkey>>>,
    balances: Arc<RwLock<BalanceState>>,
}

impl TradingCore {
    fn load_keypair() -> PyResult<Keypair> {
        let file_content = std::fs::read_to_string("id.json")
            .map_err(|e| PyValueError::new_err(format!("Failed to read id.json: {}", e)))?;

        let key_bytes: Vec<u8> = serde_json::from_str(&file_content)
            .map_err(|e| PyValueError::new_err(format!("Failed to parse JSON array: {}", e)))?;

        Keypair::from_bytes(&key_bytes)
            .map_err(|e| PyValueError::new_err(format!("Failed to create keypair: {}", e)))
    }

    async fn ensure_ata_exists(&self, mint: &Pubkey) -> PyResult<Pubkey> {
        let ata = get_associated_token_address(&self.keypair.pubkey(), mint);

        // Check cache first
        if let Some(cached_ata) = self.ata_cache.read().await.get(&ata.to_string()) {
            return Ok(*cached_ata);
        }

        match self.client.get_account(&ata).await {
            Ok(_) => {
                info!("✅ ATA exists: {}", ata);
            }
            Err(_) => {
                info!("🔄 Creating ATA for mint: {}", mint);
                let ix = create_associated_token_account_idempotent(
                    &self.keypair.pubkey(),
                    &self.keypair.pubkey(),
                    mint,
                    &spl_token::ID,
                );

                let blockhash = self.client.get_latest_blockhash().await
                    .map_err(|e| PyValueError::new_err(format!("Blockhash error: {}", e)))?;

                let mut tx = Transaction::new_with_payer(&[ix], Some(&self.keypair.pubkey()));
                tx.sign(&[&*self.keypair], blockhash);

                self.client.send_transaction_with_config(&tx, RpcSendTransactionConfig {
                    skip_preflight: true,
                    preflight_commitment: Some(CommitmentLevel::Confirmed),
                    ..Default::default()
                }).await
                .map_err(|e| PyValueError::new_err(format!("ATA creation failed: {}", e)))?;
            }
        }

        self.ata_cache.write().await.insert(ata.to_string(), ata);
        Ok(ata)
    }

    fn decode_ray_log(&self, data: &[u8]) -> PyResult<(bool, u64, String, String)> {
        if data.len() < 104 {
            return Err(PyValueError::new_err("Invalid ray_log length"));
        }

        let instruction = data[0];
        if instruction != 0x03 && instruction != 0x09 {
            return Err(PyValueError::new_err("Invalid instruction type"));
        }

        let is_buy = instruction == 0x09;
        let amount = u64::from_le_bytes(data[1..9].try_into().unwrap());

        let token_a = bs58::encode(&data[40..72]).into_string();
        let token_b = bs58::encode(&data[72..104]).into_string();

        Ok((is_buy, amount, token_a, token_b))
    }
    impl TradingCore {
        async fn execute_trade_internal(&self, params: &TradeParams) -> PyResult<String> {
            // Validate and prepare token mints
            let token_a = Pubkey::from_str(&params.token_a_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token A mint: {}", e)))?;
            let token_b = Pubkey::from_str(&params.token_b_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token B mint: {}", e)))?;
    
            // Setup ATAs based on trade direction
            let (source_ata, dest_ata) = if params.is_buy {
                (
                    self.ensure_ata_exists(&token_a).await?,
                    self.ensure_ata_exists(&token_b).await?
                )
            } else {
                (
                    self.ensure_ata_exists(&token_b).await?,
                    self.ensure_ata_exists(&token_a).await?
                )
            };
    
            // Parse Raydium accounts
            let accounts: Vec<Pubkey> = params.accounts.iter()
                .map(|acc| Pubkey::from_str(acc)
                    .map_err(|e| PyValueError::new_err(format!("Invalid account: {}", e))))
                .collect::<Result<Vec<_>, _>>()?;
    
            // Prepare instruction data
            let mut data = vec![if params.is_buy { 0x09 } else { 0x0a }];
            
            if params.is_buy {
                if let Some(ref victim_data) = params.victim_data {
                    data.extend_from_slice(&victim_data[9..]);
                } else if let Some(amount) = params.amount_in {
                    data.extend_from_slice(&amount.to_le_bytes());
                    data.extend_from_slice(&0u64.to_le_bytes()); // Min amount out
                }
            } else if params.sell_all {
                data.extend_from_slice(&u64::MAX.to_le_bytes());
                data.extend_from_slice(&0u64.to_le_bytes());
            }
    
            let account_metas = vec![
                AccountMeta::new(accounts[0], false),           // Raydium Program
                AccountMeta::new(accounts[1], false),           // AMM
                AccountMeta::new_readonly(accounts[2], false),  // AMM Authority
                AccountMeta::new(accounts[3], false),           // AMM Open Orders
                AccountMeta::new(accounts[4], false),           // AMM Target Orders
                AccountMeta::new(accounts[5], false),           // Pool Token A
                AccountMeta::new(accounts[6], false),           // Pool Token B
                AccountMeta::new_readonly(accounts[7], false),  // Serum Program
                AccountMeta::new(accounts[8], false),           // Serum Market
                AccountMeta::new(accounts[9], false),           // Serum Bids
                AccountMeta::new(accounts[10], false),          // Serum Asks
                AccountMeta::new(accounts[11], false),          // Event Queue
                AccountMeta::new(accounts[12], false),          // Base Vault
                AccountMeta::new(accounts[13], false),          // Quote Vault
                AccountMeta::new_readonly(accounts[14], false), // Vault Signer
                AccountMeta::new(source_ata, false),            // User Source
                AccountMeta::new(dest_ata, false),              // User Destination
                AccountMeta::new_readonly(self.keypair.pubkey(), true), // User Owner
            ];
    
            let swap_ix = Instruction {
                program_id: accounts[0],
                accounts: account_metas,
                data,
            };
    
            // Build transaction
            let ixs = vec![
                ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_UNITS),
                ComputeBudgetInstruction::set_compute_unit_price(PRIORITY_FEE),
                swap_ix,
            ];
    
            let recent_blockhash = self.client.get_latest_blockhash().await
                .map_err(|e| PyValueError::new_err(format!("Blockhash error: {}", e)))?;
    
            let message = Message::new_with_blockhash(
                &ixs,
                Some(&self.keypair.pubkey()),
                &recent_blockhash,
            );
    
            let mut tx = Transaction::new_unsigned(message);
            tx.try_sign(&[&*self.keypair], recent_blockhash)
                .map_err(|e| PyValueError::new_err(format!("Signing error: {}", e)))?;
    
            // Send with minimal retries for atomicity
            self.client
                .send_transaction_with_config(&tx, RpcSendTransactionConfig {
                    skip_preflight: true,
                    preflight_commitment: Some(CommitmentLevel::Processed),
                    encoding: None,
                    max_retries: Some(0),
                    min_context_slot: None,
                })
                .await
                .map(|sig| sig.to_string())
                .map_err(|e| PyValueError::new_err(format!("Transaction failed: {}", e)))
        }
    }
    
    #[pymethods]
    impl TradeParams {
        #[new]
        fn new(
            accounts: Vec<String>,
            amount_in: Option<u64>,
            is_buy: bool,
            token_a_mint: String,
            token_b_mint: String,
            victim_data: Option<Vec<u8>>,
            sell_all: bool,
        ) -> Self {
            Self {
                accounts,
                amount_in,
                is_buy,
                token_a_mint,
                token_b_mint,
                victim_data,
                sell_all,
            }
        }
    }
}    
#[pymethods]
impl TradingCore {
        #[new]
        fn new(rpc_url: String) -> PyResult<Self> {
            let keypair = Arc::new(Self::load_keypair()?);
            info!("Using keypair: {}", keypair.pubkey());
    
            Ok(Self {
                client: Arc::new(RpcClient::new_with_commitment(
                    rpc_url,
                    CommitmentConfig::confirmed(),
                )),
                keypair: keypair.clone(),
                ata_cache: Arc::new(RwLock::new(HashMap::new())),
                balances: Arc::new(RwLock::new(BalanceState {
                    sol: 0.0,
                    wsol: 0.0,
                    updated_at: 0,
                })),
            })
        }
    
        #[pyo3(name = "execute_trade_py")]
        fn execute_trade_py<'py>(
            &self,
            params: &TradeParams,
            py: Python<'py>,
        ) -> PyResult<&'py PyAny> {
            let params = params.clone();
            let this = self.clone();
            
            future_into_py(py, async move {
                this.execute_trade_internal(&params).await
            })
        }
    
        #[pyo3(name = "decode_ray_log_py")]
        fn decode_ray_log_py(&self, data: Vec<u8>) -> PyResult<(bool, u64, String, String)> {
            self.decode_ray_log(&data)
        }
    
        #[pyo3(name = "get_sol_balance")]
        fn get_sol_balance_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
            let this = self.clone();
            
            future_into_py(py, async move {
                let balance = this.client.get_balance(&this.keypair.pubkey()).await
                    .map_err(|e| PyValueError::new_err(format!("Failed to get SOL balance: {}", e)))?;
                Ok(balance as f64 / LAMPORTS_PER_SOL as f64)
            })
        }
    
        #[pyo3(name = "get_wsol_balance")]
        fn get_wsol_balance_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
            let this = self.clone();
            
            future_into_py(py, async move {
                if let Some(wsol_account) = this.ata_cache.read().await.get(WSOL_MINT) {
                    let balance = this.client.get_token_account_balance(wsol_account).await
                        .map_err(|e| PyValueError::new_err(format!("Failed to get WSOL balance: {}", e)))?;
                    
                    let amount = balance.amount.parse::<u64>()
                        .map_err(|e| PyValueError::new_err(format!("Invalid balance format: {}", e)))?;
                    
                    Ok(amount as f64 / LAMPORTS_PER_SOL as f64)
                } else {
                    Ok(0.0)
                }
            })
        }
    
        #[pyo3(name = "get_pubkey")]
        fn get_pubkey(&self) -> String {
            self.keypair.pubkey().to_string()
        }
    }

#[pymodule]
    fn solana_rust_bot(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
        pyo3_log::init();
        info!("🚀 Initializing Solana Rust Bot...");
        m.add_class::<TradingCore>()?;
        m.add_class::<TradeParams>()?;
        info!("✅ Bot initialized successfully");
        Ok(())
    }  please will you fix the spacing issues im having and write the full script for me please do not change my code all your doing is writing it exactly how i have my code just without spacing and bracket issues