use base58::FromBase58;
use dotenv::dotenv;
use log::{error, info, warn};
use pyo3::{exceptions::PyValueError, prelude::*};
use pyo3_asyncio::tokio::future_into_py;
use solana_client::{
    nonblocking::rpc_client::RpcClient,
    rpc_config::RpcSendTransactionConfig,
    client_error::ClientError,
};
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::{
    collections::HashMap,
    env,
    str::FromStr,
    sync::Arc,
    time::{Duration, SystemTime},
};
use tokio::sync::RwLock;

// Custom error handling
#[derive(Debug)]
pub enum BotError {
    ATAError(String),
    TradeError(String),
    ConfigError(String),
    RPCError(String),
    WebSocketError(String),
    ValidationError(String),
}

impl std::fmt::Display for BotError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BotError::ATAError(msg) => write!(f, "ATA Error: {}", msg),
            BotError::TradeError(msg) => write!(f, "Trade Error: {}", msg),
            BotError::ConfigError(msg) => write!(f, "Config Error: {}", msg),
            BotError::RPCError(msg) => write!(f, "RPC Error: {}", msg),
            BotError::WebSocketError(msg) => write!(f, "WebSocket Error: {}", msg),
            BotError::ValidationError(msg) => write!(f, "Validation Error: {}", msg),
        }
    }
}

impl From<BotError> for PyErr {
    fn from(err: BotError) -> PyErr {
        PyValueError::new_err(err.to_string())
    }
}

impl From<ClientError> for BotError {
    fn from(err: ClientError) -> Self {
        BotError::RPCError(err.to_string())
    }
}

// Constants loader with validation
fn load_constants() -> Result<HashMap<String, String>, BotError> {
    dotenv().ok();
    let mut constants = HashMap::new();

    let required_vars = vec![
        "COMPUTE_LIMIT",
        "PRIORITY_FEE",
        "RAYDIUM_PROGRAM_ID",
        "AMM_ID",
        "MARKET_AMM_AUTHORITY",
        "MARKET_AMM_OPEN_ORDERS",
        "MARKET_AMM_TARGET",
        "MARKET_POOL_TOKEN_ACCOUNT",
        "MARKET_SERUM_MARKET",
        "MARKET_SERUM_BIDS",
        "MARKET_SERUM_ASKS",
        "MARKET_EVENT_QUEUE",
        "MARKET_BASE_VAULT",
        "MARKET_QUOTE_VAULT",
        "BASE_VAULT_SIGNER",
    ];

    for var in required_vars {
        let value = env::var(var).map_err(|e| {
            BotError::ConfigError(format!("Missing required environment variable {}: {}", var, e))
        })?;

        // Validate Pubkey format for addresses
        if var.contains("_ID") || var.contains("ACCOUNT") || var.contains("VAULT") || var.contains("MARKET") {
            if value == "11111111111111111111111111111111" {
                return Err(BotError::ConfigError(
                    format!("{} cannot be system program address", var)
                ));
            }

            Pubkey::from_str(&value).map_err(|e| {
                BotError::ConfigError(format!("Invalid pubkey for {}: {}", var, e))
            })?;
        }

        constants.insert(var.to_string(), value);
    }

    Ok(constants)
}

#[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,
    #[pyo3(get, set)]
    pub slippage_bps: u64,
    #[pyo3(get, set)]
    pub priority_fee: u64,
    #[pyo3(get, set)]
    pub compute_limit: u32,
}

#[pyclass]
#[derive(Clone)]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    constants: Arc<HashMap<String, String>>,
    ata_cache: Arc<RwLock<HashMap<String, Pubkey>>>,
    last_trade_time: Arc<RwLock<SystemTime>>,
    active_trade: Arc<RwLock<bool>>,
}
impl TradingCore {
    fn load_keypair() -> Result<Keypair, BotError> {
        let private_key = env::var("SOLANA_PRIVATE_KEY")
            .map_err(|_| BotError::ConfigError("SOLANA_PRIVATE_KEY not found".to_string()))?;

        let secret_key_bytes = private_key
            .from_base58()
            .map_err(|_| BotError::ConfigError("Invalid private key encoding".to_string()))?;

        Keypair::from_bytes(&secret_key_bytes)
            .map_err(|e| BotError::ConfigError(format!("Invalid keypair bytes: {:?}", e)))
    }

    async fn get_or_create_ata(&self, mint: &Pubkey) -> Result<Pubkey, BotError> {
        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!("✅ Using existing ATA: {}", ata);
            }
            Err(e) => {
                info!("🔄 Creating new ATA for mint: {}", mint);

                let create_ata_ix = create_associated_token_account_idempotent(
                    &self.keypair.pubkey(),
                    &self.keypair.pubkey(),
                    mint,
                    &spl_token::ID,
                );

                let recent_blockhash = self
                    .client
                    .get_latest_blockhash()
                    .await
                    .map_err(|e| BotError::RPCError(format!("Failed to get blockhash: {}", e)))?;

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

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

                match self.client.send_transaction_with_config(&tx, config).await {
                    Ok(signature) => {
                        info!("📝 ATA creation transaction sent: {}", signature);

                        // Wait for confirmation
                        tokio::time::sleep(Duration::from_millis(400)).await;

                        match self.client.get_account(&ata).await {
                            Ok(_) => info!("✅ ATA created and verified: {}", ata),
                            Err(e) => {
                                return Err(BotError::ATAError(
                                    format!("ATA creation verification failed: {}", e)
                                ))
                            }
                        }
                    }
                    Err(e) => {
                        return Err(BotError::ATAError(format!("Failed to create ATA: {}", e)))
                    }
                }
            }
        }

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

    fn create_swap_instruction(
        &self,
        params: &TradeParams,
        source_ata: Pubkey,
        dest_ata: Pubkey,
    ) -> Result<Instruction, BotError> {
        if params.accounts.len() < 15 {
            return Err(BotError::ValidationError("Insufficient accounts provided".to_string()));
        }

        let accounts: Vec<Pubkey> = params
            .accounts
            .iter()
            .enumerate()
            .map(|(i, acc)| {
                Pubkey::from_str(acc).map_err(|e| {
                    BotError::ValidationError(format!("Invalid account at position {}: {}", i, e))
                })
            })
            .collect::<Result<Vec<_>, _>>()?;

        let mut data = vec![if params.is_buy { 0x09 } else { 0x0a }];

        match (params.amount_in, params.victim_data.clone()) {
            (Some(amount), None) => {
                data.extend_from_slice(&amount.to_le_bytes());
                data.extend_from_slice(&0u64.to_le_bytes());
            }
            (None, Some(victim_data)) => {
                data.extend_from_slice(&victim_data);
            }
            _ => {
                return Err(BotError::ValidationError(
                    "Either amount_in or victim_data required".to_string()
                ))
            }
        };

        let 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 Coin Token Account
            AccountMeta::new(accounts[6], false),          // Pool PC Token Account
            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),         // Serum 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 Token Account
            AccountMeta::new(dest_ata, false),             // User Destination Token Account
            AccountMeta::new_readonly(self.keypair.pubkey(), true), // User Owner
        ];

        Ok(Instruction {
            program_id: accounts[0],
            accounts: metas,
            data,
        })
    }

    async fn execute_trade_internal(&self, params: &TradeParams) -> Result<String, BotError> {
        let token_a = Pubkey::from_str(&params.token_a_mint)
            .map_err(|e| BotError::ValidationError(format!("Invalid token A mint: {}", e)))?;
        let token_b = Pubkey::from_str(&params.token_b_mint)
            .map_err(|e| BotError::ValidationError(format!("Invalid token B mint: {}", e)))?;

        // Get ATAs with retries
        let mut retries = 3;
        let (source_ata, dest_ata) = loop {
            match if params.is_buy {
                Ok((
                    self.get_or_create_ata(&token_a).await?,
                    self.get_or_create_ata(&token_b).await?,
                ))
            } else {
                Ok((
                    self.get_or_create_ata(&token_b).await?,
                    self.get_or_create_ata(&token_a).await?,
                ))
            } {
                Ok(atas) => break atas,
                Err(e) if retries > 0 => {
                    retries -= 1;
                    warn!("ATA creation retry {} after error: {}", 3 - retries, e);
                    tokio::time::sleep(Duration::from_millis(200)).await;
                    continue;
                }
                Err(e) => return Err(e),
            }
        };

        let swap_ix = self.create_swap_instruction(params, source_ata, dest_ata)?;

        let ixs = vec![
            ComputeBudgetInstruction::set_compute_unit_limit(params.compute_limit),
            ComputeBudgetInstruction::set_compute_unit_price(params.priority_fee),
            swap_ix,
        ];

        let recent_blockhash = self
            .client
            .get_latest_blockhash()
            .await
            .map_err(|e| BotError::RPCError(format!("Blockhash error: {}", e)))?;

        let message = Message::new_with_blockhash(
            &ixs,
            Some(&self.keypair.pub
                #[pymethods]
                impl TradingCore {
                    #[new]
                    fn new(rpc_url: &str) -> PyResult<Self> {
                        let constants = load_constants().map_err(|e| PyValueError::new_err(e.to_string()))?;
                        let keypair = Arc::new(Self::load_keypair().map_err(|e| PyValueError::new_err(e.to_string()))?);
                
                        info!("🔑 Using keypair: {}", keypair.pubkey());
                        info!("✅ Loaded configuration from environment");
                
                        Ok(Self {
                            client: Arc::new(RpcClient::new_with_commitment(
                                rpc_url.to_string(),
                                CommitmentConfig::processed(),
                            )),
                            keypair,
                            constants: Arc::new(constants),
                            ata_cache: Arc::new(RwLock::new(HashMap::new())),
                            last_trade_time: Arc::new(RwLock::new(SystemTime::now())),
                            active_trade: Arc::new(RwLock::new(false)),
                        })
                    }
                
                    #[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 {
                            let result = this.execute_trade_internal(&params).await;
                            result.map_err(|e| PyValueError::new_err(e.to_string()))
                        })
                    }
                
                    #[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 / 1_000_000_000.0)
                        })
                    }
                
                    #[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 {
                            let wsol_account = env::var("WSOL_TOKEN_ACCOUNT")
                                .map_err(|_| PyValueError::new_err("WSOL_TOKEN_ACCOUNT not found"))?;
                
                            let pubkey = Pubkey::from_str(&wsol_account)
                                .map_err(|e| PyValueError::new_err(format!("Invalid WSOL account: {}", e)))?;
                            
                            let balance = this
                                .client
                                .get_token_account_balance(&pubkey)
                                .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 / 1_000_000_000.0)
                        })
                    }
                
                    #[pyo3(name = "decode_ray_log_py")]
                    fn decode_ray_log(&self, data: &[u8]) -> PyResult<(String, String, u64)> {
                        if data.len() < 57 {
                            return Err(PyValueError::new_err("Invalid Raydium V4 log length"));
                        }
                
                        let instruction_type = data[0];
                        if instruction_type != 0x03 {
                            return Err(PyValueError::new_err(format!(
                                "Invalid swap instruction: {:#x}",
                                instruction_type
                            )));
                        }
                
                        let amount = u64::from_le_bytes(data[1..9].try_into().unwrap());
                
                        let token_a = match Pubkey::try_from(&data[24..56]) {
                            Ok(pubkey) => pubkey.to_string(),
                            Err(_) => return Err(PyValueError::new_err("Invalid token A mint")),
                        };
                
                        let token_b = "So11111111111111111111111111111111111111112".to_string();
                
                        Ok((token_a, token_b, amount))
                    }
                
                    #[pyo3(name = "get_token_account_info")]
                    fn get_token_account_info_py<'py>(&self, token_mint: &str, py: Python<'py>) -> PyResult<&'py PyAny> {
                        let this = self.clone();
                        let token_mint = token_mint.to_string();
                
                        future_into_py(py, async move {
                            let mint_pubkey = Pubkey::from_str(&token_mint)
                                .map_err(|e| PyValueError::new_err(format!("Invalid token mint: {}", e)))?;
                
                            let ata = get_associated_token_address(&this.keypair.pubkey(), &mint_pubkey);
                            
                            match this.client.get_account(&ata).await {
                                Ok(account) => Ok(account.lamports as f64 / 1_000_000_000.0),
                                Err(e) => Err(PyValueError::new_err(format!("Failed to get token account info: {}", e))),
                            }
                        })
                    }
                
                    #[pyo3(name = "get_pubkey")]
                    fn get_pubkey(&self) -> String {
                        self.keypair.pubkey().to_string()
                    }
                
                    #[pyo3(name = "is_trade_active")]
                    fn is_trade_active_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
                        let this = self.clone();
                
                        future_into_py(py, async move {
                            Ok(*this.active_trade.read().await)
                        })
                    }
                
                    #[pyo3(name = "time_since_last_trade")]
                    fn time_since_last_trade_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
                        let this = self.clone();
                
                        future_into_py(py, async move {
                            let now = SystemTime::now();
                            let last = *this.last_trade_time.read().await;
                            Ok(now.duration_since(last).unwrap_or_default().as_secs_f64())
                        })
                    }
                }
                
                #[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(())
                } here fix the spacing please write the full script