use log::{info, warn};
use pyo3::{exceptions::PyValueError, prelude::*};
use pyo3_asyncio::tokio::future_into_py;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
    commitment_config::{CommitmentConfig, CommitmentLevel},
    compute_budget::ComputeBudgetInstruction,
    instruction::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 spl_token;
use std::{
    env,
    str::FromStr,
    sync::Arc,
};
use base58::FromBase58;

// Constants
const COMPUTE_UNITS: u32 = 50_000;
const PRIORITY_FEE: u64 = 750_000;

#[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 reuse_blockhash: bool,
    #[pyo3(get, set)]
    pub token_a_mint: String,
    #[pyo3(get, set)]
    pub token_b_mint: String,
    #[pyo3(get, set)]
    pub sell_all: bool,
    #[pyo3(get, set)]
    pub victim_data: Option<Vec<u8>>,
}

#[pyclass]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
}

impl TradingCore {
    /// Load keypair from environment variables
    fn load_keypair_from_env() -> PyResult<Keypair> {
        let private_key = env::var("SOLANA_PRIVATE_KEY")
            .map_err(|_| PyValueError::new_err("SOLANA_PRIVATE_KEY not found in environment"))?;
        let public_key = env::var("SOLANA_PUBLIC_KEY")
            .map_err(|_| PyValueError::new_err("SOLANA_PUBLIC_KEY not found in environment"))?;
        // Validate public key
        let _pubkey = Pubkey::from_str(&public_key)
            .map_err(|e| PyValueError::new_err(format!("Invalid public key: {}", e)))?;
        // Decode private key (assumes Base58 encoding)
        let secret_key_bytes = private_key.from_base58()
            .map_err(|e| PyValueError::new_err(format!("Invalid private key encoding: {:?}", e)))?;
        Keypair::from_bytes(&secret_key_bytes)
            .map_err(|e| PyValueError::new_err(format!("Invalid keypair: {}", e)))
    }

    /// Ensure Associated Token Account (ATA) exists
    async fn ensure_ata_exists(
        &self, 
        owner: &Pubkey, 
        mint: &Pubkey
    ) -> PyResult<Pubkey> {
        let ata = get_associated_token_address(owner, mint);
        match self.client.get_account(&ata).await {
            Ok(_) => {
                info!("ATA already exists: {}", ata);
                Ok(ata)
            }
            Err(_) => {
                warn!("ATA does not exist. Creating ATA for mint: {}", mint);
                // Create instruction using spl_associated_token_account
                let ix = spl_associated_token_account::instruction::create_associated_token_account_idempotent(
                    &self.keypair.pubkey(),  // Funding account (fee payer)
                    owner,                   // Wallet address for new ATA
                    mint,                    // Token mint address
                    &spl_token::ID,          // Token program ID
                );
                let blockhash = self.client.get_latest_blockhash().await
                    .map_err(|e| PyValueError::new_err(format!("Failed to get blockhash: {}", e)))?;
                // Create and sign transaction
                let mut tx = Transaction::new_with_payer(&[ix], Some(&self.keypair.pubkey()));
                tx.sign(&[self.keypair.as_ref()], blockhash);
                // Send transaction with preflight disabled
                let config = solana_client::rpc_config::RpcSendTransactionConfig {
                    skip_preflight: true,
                    preflight_commitment: Some(CommitmentLevel::Confirmed),
                    encoding: None,
                    max_retries: Some(3),
                    min_context_slot: None,
                };
                match self.client.send_transaction_with_config(&tx, config).await {
                    Ok(_) => {
                        info!("ATA created successfully: {}", ata);
                        Ok(ata)
                    },
                    Err(e) => {
                        warn!("ATA creation failed with error: {:?}", e);
                        Err(PyValueError::new_err(format!("Failed to create ATA. Full error: {:?}", e)))
                    }
                }
            }
        }
    }

    /// Create custom swap instruction
    fn create_swap_instruction(
        &self,
        program_id: &Pubkey,
        accounts: &[Pubkey],
        amount: u64,
        is_buy: bool,
        source_ata: Pubkey,
        destination_ata: Pubkey,
        victim_data: Option<&[u8]>,
    ) -> Instruction {
        let mut modified_accounts = accounts.to_vec();

        // Modify accounts to use ATAs; ensure at least three accounts exist.
        if modified_accounts.len() < 3 {
            // If insufficient accounts provided, pad with our keypair pubkey.
            modified_accounts.resize(3, self.keypair.pubkey());
        }

        if is_buy {
            modified_accounts[1] = source_ata;      // Source WSOL ATA
            modified_accounts[2] = destination_ata;  // Destination Token ATA
        } else {
            modified_accounts[1] = source_ata;      // Source Token ATA
            modified_accounts[2] = destination_ata;  // Destination WSOL ATA
        }
        // Prepare instruction data
        let mut data = vec![if is_buy { 9u8 } else { 11u8 }];
        if is_buy {
            if let Some(vdata) = victim_data {
                data.extend_from_slice(vdata);
            } else {
                data.extend_from_slice(&amount.to_le_bytes());
            }
        } else {
            data.extend_from_slice(&amount.to_le_bytes());
        }
        // Create account metas dynamically. Pad to at least 18 accounts if needed.
        if modified_accounts.len() < 18 {
            modified_accounts.resize(18, self.keypair.pubkey());
        }
        let account_metas: Vec<solana_sdk::instruction::AccountMeta> = modified_accounts
            .iter()
            .enumerate()
            .map(|(i, &pubkey)| {
                match i {
                    0 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // Program ID
                    1 => solana_sdk::instruction::AccountMeta::new(pubkey, true),           // AMM Authority (signer)
                    2 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // AMM Open Orders
                    3 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // AMM Target 
                    4 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Pool Token Mint
                    5 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Pool Token Account
                    6 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Program ID
                    7 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Market
                    8 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Bids
                    9 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Asks
                    10 => solana_sdk::instruction::AccountMeta::new(pubkey, false),         // Serum Event Queue
                    11 => solana_sdk::instruction::AccountMeta::new(pubkey, false),         // Serum Base Vault
                    12 => solana_sdk::instruction::AccountMeta::new(pubkey, false),         // Serum Quote Vault
                    13 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // Base Vault Signer
                    14 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // Quote Vault Signer
                    15 => solana_sdk::instruction::AccountMeta::new(pubkey, true),          // User Source Token Account
                    16 => solana_sdk::instruction::AccountMeta::new(pubkey, true),          // User Destination Token Account
                    17 => solana_sdk::instruction::AccountMeta::new(pubkey, true),          // User Owner
                    _ => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false),  // Any additional accounts
                }
            })
            .collect();

        Instruction {
            program_id: *program_id,
            accounts: account_metas,
            data,
        }
    }
}

#[pymethods]
impl TradeParams {
    #[new]
    #[pyo3(signature = (
        accounts,
        amount_in=None,
        is_buy=false,
        reuse_blockhash=false,
        token_a_mint="".to_string(),
        token_b_mint="".to_string(),
        sell_all=false,
        victim_data=None,
    ))]
    fn new(
        accounts: Vec<String>,
        amount_in: Option<u64>,
        is_buy: bool,
        reuse_blockhash: bool,
        token_a_mint: String,
        token_b_mint: String,
        sell_all: bool,
        victim_data: Option<Vec<u8>>,
    ) -> Self {
        Self {
            accounts,
            amount_in,
            is_buy,
            reuse_blockhash,
            token_a_mint,
            token_b_mint,
            sell_all,
            victim_data,
        }
    }
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(rpc_url: &str) -> PyResult<Self> {
        // Load keypair from environment variables
        let keypair = Self::load_keypair_from_env()?;
        info!("Loaded keypair with public key: {}", keypair.pubkey());

        Ok(Self {
            client: Arc::new(RpcClient::new_with_commitment(
                rpc_url.to_string(),
                CommitmentConfig::processed(),
            )),
            keypair: Arc::new(keypair),
        })
    }

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

        future_into_py(py, async move {
            // Convert account strings to pubkeys
            let mut accounts: Vec<Pubkey> = Vec::with_capacity(params.accounts.len());
            for (idx, acc) in params.accounts.iter().enumerate() {
                let pubkey = Pubkey::from_str(acc)
                    .map_err(|e| PyValueError::new_err(format!("Invalid account address at index {}: {}", idx, e)))?;
                accounts.push(pubkey);
            }

            // Prepare token mints
            let token_a_mint = Pubkey::from_str(&params.token_a_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token A mint: {}", e)))?;
            let token_b_mint = Pubkey::from_str(&params.token_b_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token B mint: {}", e)))?;

            // Ensure ATAs exist
            let source_ata = if params.is_buy {
                // For buy, source is WSOL ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_b_mint).await?
            } else {
                // For sell, source is token A ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_a_mint).await?
            };

            let destination_ata = if params.is_buy {
                // For buy, destination is token A ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_a_mint).await?
            } else {
                // For sell, destination is WSOL ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_b_mint).await?
            };

            let mut instructions = vec![];

            // Add compute budget instructions
            instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_UNITS));
            instructions.push(ComputeBudgetInstruction::set_compute_unit_price(PRIORITY_FEE));

            // Determine trade amount
            let amount = if let Some(amount) = params.amount_in {
                amount
            } else if params.sell_all {
                // Get token balance for sell_all
                let balance = self_clone.client.get_token_account_balance(&source_ata)
                    .await
                    .map_err(|e| PyValueError::new_err(format!("Failed to get token balance: {}", e)))?
                    .amount
                    .parse::<u64>()
                    .map_err(|e| PyValueError::new_err(format!("Invalid token balance: {}", e)))?;
                balance
            } else {
                return Err(PyValueError::new_err("No amount specified for swap"));
            };

            // Create swap instruction with ATAs
            // Use accounts[0] as the program id (should be provided)
            let swap_ix = self_clone.create_swap_instruction(
                &accounts[0],  // Program ID
                &accounts,     // Original accounts
                amount,
                params.is_buy,
                source_ata,
                destination_ata,
                params.victim_data.as_deref(),
            );

            instructions.push(swap_ix);

            // Get recent blockhash
            let blockhash = self_clone.client.get_latest_blockhash()
                .await
                .map_err(|e| PyValueError::new_err(format!("Failed to get blockhash: {}", e)))?;

            // Create transaction message
            let message = Message::new_with_blockhash(
                &instructions, 
                Some(&self_clone.keypair.pubkey()), 
                &blockhash,
            );

            // Create and sign transaction
            let mut tx = Transaction::new_unsigned(message);
            tx.try_sign(&[self_clone.keypair.as_ref()], blockhash)
                .map_err(|e| PyValueError::new_err(format!("Transaction signing failed: {:?}", e)))?;

            // Send transaction
            let signature = self_clone.client.send_transaction_with_config(
                &tx,
                solana_client::rpc_config::RpcSendTransactionConfig {
                    let signature = self_clone.client.send_transaction_with_config(
                        &tx,
                        solana_client::rpc_config::RpcSendTransactionConfig {
                            skip_preflight: true,
                            preflight_commitment: Some(CommitmentLevel::Processed),
                            encoding: None,
                            max_retries: None,
                            min_context_slot: None,
                        },
                    )
                    .await
                    .map_err(|e| PyValueError::new_err(format!("Transaction failed: {}", e)))?;
                    
                    Ok(signature.to_string())
                })
            }
        
            #[pyo3(name = "get_sol_balance")]
            fn get_sol_balance_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
                 let self_clone = Self {
                    client: self.client.clone(),
                    keypair: self.keypair.clone(),
                 };
                 
                 future_into_py(py, async move {
                    let balance = self_clone.client.get_balance(&self_clone.keypair.pubkey())
                        .await
                        .map_err(|e| PyValueError::new_err(format!("Failed to get SOL balance: {}", e)))?;
                    Ok(balance as f64 / 1e9)
                 })
            }
        
            #[pyo3(name = "get_wsol_balance")]
            fn get_wsol_balance_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
                 let self_clone = Self {
                    client: self.client.clone(),
                    keypair: self.keypair.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 = self_clone.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 / 1e9)
                 })
            }
        }
        
        #[pymodule]
        fn solana_rust_bot(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
            pyo3_log::init();
            m.add_class::<TradingCore>()?;
            m.add_class::<TradeParams>()?;
            Ok(())
        }
         fix the spacing please