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_idempotent,
};
use spl_token::ID as SPL_TOKEN_PROGRAM_ID;
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,
    #[pyo3(get, set)]
    pub amount_in: Option,
    #[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>,
}

#[pyclass]
pub struct TradingCore {
    client: Arc,
    keypair: Arc,
}

impl TradingCore {
    /// Load keypair from environment variables
    fn load_keypair_from_env() -> PyResult {
        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)))
    }

    /// Check the SOL balance of the keypair
    async fn check_sol_balance(&self) -> PyResult {
        let balance = self.client.get_balance(&self.keypair.pubkey()).await
            .map_err(|e| PyValueError::new_err(format!("Failed to get SOL balance: {}", e)))?;
        Ok(balance)
    }

    /// Ensure Associated Token Account (ATA) exists
    async fn ensure_ata_exists(
        &self, 
        owner: &Pubkey, 
        mint: &Pubkey
    ) -> PyResult {
        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 = create_associated_token_account_idempotent(
                    &self.keypair.pubkey(), // Funding account (fee payer)
                    owner,                  // Wallet address for new ATA
                    mint,                   // Token mint address
                    &SPL_TOKEN_PROGRAM_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();

        // Ensure minimum account length
        if modified_accounts.len() < 3 {
            modified_accounts.resize(3, self.keypair.pubkey());
        }

        // Modify accounts to use ATAs
        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 and pad to at least 18 accounts if needed
        if modified_accounts.len() < 18 {
            modified_accounts.resize(18, self.keypair.pubkey());
        }

        let account_metas: Vec = 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_readonly(pubkey, false), // AMM Authority (NOT a 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, false),          // User Source Token Account (not a signer)
                    16 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // User Destination Token Account (not a signer)
                    17 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, true),  // User Owner (signer)
                    _ => 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,
        amount_in: Option,
        is_buy: bool,
        reuse_blockhash: bool,
        token_a_mint: String,
        token_b_mint: String,
        sell_all: bool,
        victim_data: Option>,
    ) -> 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 {
        // 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 = 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(¶ms.token_a_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token A mint: {}", e)))?;
            let token_b_mint = Pubkey::from_str(¶ms.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 {
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_b_mint).await?
            } else {
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_a_mint).await?
            };
    
            let destination_ata = if params.is_buy {
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_a_mint).await?
            } else {
                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 using 0 when sell_all is true
            let amount = if params.sell_all {
                0  // Use 0 as "sell all" indicator
            } else if let Some(amount) = params.amount_in {
                amount
            } else {
                return Err(PyValueError::new_err("No amount specified for swap"));
            };
    
            // Create swap instruction with ATAs
            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 the 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 {
                    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::()
                .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::()?;
    m.add_class::()?;
    Ok(())
}  Compiling solana_rust_bot v0.1.0 (C:\solana_rust_bot)
error: unknown start of token: \u{b6}
   --> src\lib.rs:283:49
    |
283 |             let token_a_mint = Pubkey::from_str(¶ms.token_a_mint)
    |                                                 ^

error: unknown start of token: \u{b6}
   --> src\lib.rs:285:49
    |
285 |             let token_b_mint = Pubkey::from_str(¶ms.token_b_mint)
    |                                                 ^

error: expected `,`, or `}`, found `>`
  --> src\lib.rs:43:28
   |
27 | pub struct TradeParams {
   |            ----------- while parsing this struct
...
43 |     pub victim_data: Option>,
   |                            ^

error: expected parameter name, found `>`
   --> src\lib.rs:234:28
    |
234 |         victim_data: Option>,
    |                            ^ expected parameter name

error: expected one of `!`, `(`, `)`, `+`, `,`, `::`, or `<`, found `>`
   --> src\lib.rs:234:28
    |
234 |         victim_data: Option>,
    |                            ^
    |                            |
    |                            expected one of 7 possible tokens
    |                            help: missing `,`

error: field expressions cannot have generic arguments
   --> src\lib.rs:395:41
    |
395 |             let amount = balance.amount.parse::()
    |                                         ^^^^^^^^^

error: field expressions cannot have generic arguments
   --> src\lib.rs:405:7
    |
405 |     m.add_class::()?;
    |       ^^^^^^^^^^^^^

error: field expressions cannot have generic arguments
   --> src\lib.rs:406:7
    |
406 |     m.add_class::()?;
    |       ^^^^^^^^^^^^^

error: expected `,`
  --> src\lib.rs:43:28
   |
43 |     pub victim_data: Option>,
   |                            ^

error: expected `,`
   --> src\lib.rs:234:28
    |
234 |         victim_data: Option>,
    |                            ^

error: expected `<`
   --> src\lib.rs:395:48
    |
395 |             let amount = balance.amount.parse::()
    |                                                ^

error: expected `<`
   --> src\lib.rs:405:18
    |
405 |     m.add_class::()?;
    |                  ^

warning: unused import: `pyo3_asyncio::tokio::future_into_py`
 --> src\lib.rs:3:5
  |
3 | use pyo3_asyncio::tokio::future_into_py;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `solana_client::nonblocking::rpc_client::RpcClient`
 --> src\lib.rs:4:5
  |
4 | use solana_client::nonblocking::rpc_client::RpcClient;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: unused imports: `CommitmentConfig`, `compute_budget::ComputeBudgetInstruction`, and `message::Message`
 --> src\lib.rs:6:25
  |
6 |     commitment_config::{CommitmentConfig, CommitmentLevel},
  |                         ^^^^^^^^^^^^^^^^
7 |     compute_budget::ComputeBudgetInstruction,
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 |     instruction::Instruction,
9 |     message::Message,
  |     ^^^^^^^^^^^^^^^^

error[E0107]: missing generics for struct `Arc`
   --> src\lib.rs:48:13
    |
48  |     client: Arc,
    |             ^^^ expected at least 1 generic argument
    |
note: struct defined here, with at least 1 generic parameter: `T`
   --> C:\Users\Orsen\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\alloc\src\sync.rs:242:12
    |
242 | pub struct Arc<
    |            ^^^
243 |     T: ?Sized,
    |     -
help: add missing generic argument
    |
48  |     client: Arc<T>,
    |                +++

error[E0107]: missing generics for struct `Arc`
   --> src\lib.rs:49:14
    |
49  |     keypair: Arc,
    |              ^^^ expected at least 1 generic argument
    |
note: struct defined here, with at least 1 generic parameter: `T`
   --> C:\Users\Orsen\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\alloc\src\sync.rs:242:12
    |
242 | pub struct Arc<
    |            ^^^
243 |     T: ?Sized,
    |     -
help: add missing generic argument
    |
49  |     keypair: Arc<T>,
    |                 +++

error[E0107]: missing generics for type alias `pyo3::PyResult`
  --> src\lib.rs:54:35
   |
54 |     fn load_keypair_from_env() -> PyResult {
   |                                   ^^^^^^^^ expected 1 generic argument
   |
note: type alias defined here, with 1 generic parameter: `T`
  --> C:\Users\Orsen\.cargo\registry\src\index.crates.io-6f17d22bba15001f\pyo3-0.18.3\src\err\mod.rs:46:10
   |
46 | pub type PyResult<T> = Result<T, PyErr>;
   |          ^^^^^^^^ -
help: add missing generic argument
   |
54 |     fn load_keypair_from_env() -> PyResult<T> {
   |                                           +++

error[E0107]: missing generics for type alias `pyo3::PyResult`
  --> src\lib.rs:73:42
   |
73 |     async fn check_sol_balance(&self) -> PyResult {
   |                                          ^^^^^^^^ expected 1 generic argument
   |
note: type alias defined here, with 1 generic parameter: `T`
  --> C:\Users\Orsen\.cargo\registry\src\index.crates.io-6f17d22bba15001f\pyo3-0.18.3\src\err\mod.rs:46:10
   |
46 | pub type PyResult<T> = Result<T, PyErr>;
   |          ^^^^^^^^ -
help: add missing generic argument
   |
73 |     async fn check_sol_balance(&self) -> PyResult<T> {
   |                                                  +++

error[E0107]: missing generics for type alias `pyo3::PyResult`
  --> src\lib.rs:84:10
   |
84 |     ) -> PyResult {
   |          ^^^^^^^^ expected 1 generic argument
   |
note: type alias defined here, with 1 generic parameter: `T`
  --> C:\Users\Orsen\.cargo\registry\src\index.crates.io-6f17d22bba15001f\pyo3-0.18.3\src\err\mod.rs:46:10
   |
46 | pub type PyResult<T> = Result<T, PyErr>;
   |          ^^^^^^^^ -
help: add missing generic argument
   |
84 |     ) -> PyResult<T> {
   |                  +++

error[E0107]: missing generics for struct `Vec`
   --> src\lib.rs:177:28
    |
177 |         let account_metas: Vec = modified_accounts
    |                            ^^^ expected at least 1 generic argument
    |
note: struct defined here, with at least 1 generic parameter: `T`
   --> C:\Users\Orsen\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\alloc\src\vec\mod.rs:397:12
    |
397 | pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
    |            ^^^ -
help: add missing generic argument
    |
177 |         let account_metas: Vec<T> = modified_accounts
    |                               +++

For more information about this error, try `rustc --explain E0107`.
warning: `solana_rust_bot` (lib) generated 3 warnings
error: could not compile `solana_rust_bot` (lib) due to 18 previous errors; 3 warnings emitted
2025-02-09T05:00:36.213237Z  INFO build_wheels: maturin::build_context: close time.busy=5.42s time.idle=1.10µs
💥 maturin failed
  Caused by: Failed to build a native library through cargo
  Caused by: Cargo build finished with "exit code: 101": `"cargo" "rustc" "--jobs" "16" "--message-format" "json-render-diagnostics" "--manifest-path" "C:\\solana_rust_bot\\Cargo.toml" "--release" "--lib" "--crate-type" "cdylib"` fix all the issues write the full script