use pyo3::prelude::*;
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,
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_program,
    system_transaction,
};
use std::{str::FromStr, time::{SystemTime, UNIX_EPOCH}};
use std::collections::HashMap;
use tokio::runtime::Runtime;
use log::{info, error, warn};

// Constants
const MARGA_RPC_URL: &str = "https://marga-e56m84-fast-mainnet.helius-rpc.com/";
const MARGA_WS_URL: &str = "wss://marga-e56m84-fast-mainnet.helius-rpc.com/";
const SCALING_FACTOR: u64 = 1_000_000_000;
const RAYDIUM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const WSOL_MINT: &str = "So111111111111111111111111111111111111112";
const COMPUTE_BUDGET: u32 = 1_400_000;
const DEFAULT_PRIORITY_FEE: u64 = 6500;

// Priority Fee API Structures
#[derive(Debug, Serialize, Deserialize)]
struct PriorityFeeRequest {
    jsonrpc: String,
    id: String,
    method: String,
    params: Vec<PriorityFeeParams>,
}

#[derive(Debug, Serialize, Deserialize)]
struct PriorityFeeParams {
    account_keys: Vec<String>,
    options: PriorityFeeOptions,
}

#[derive(Debug, Serialize, Deserialize)]
struct PriorityFeeOptions {
    priority_level: String,
    include_all_priority_fee_levels: bool,
    recommended: bool,
}

#[derive(Debug, Serialize, Deserialize)]
struct PriorityFeeResponse {
    jsonrpc: String,
    result: PriorityFeeResult,
    id: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct PriorityFeeResult {
    priority_fee_estimate: f64,
}

// Pool Metrics Structure
#[pyclass]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolMetrics {
    #[pyo3(get)]
    pub address: String,
    #[pyo3(get)]
    pub base_supply: u64,
    #[pyo3(get)]
    pub quote_supply: u64,
    #[pyo3(get)]
    pub volume_24h: u64,
    #[pyo3(get)]
    pub liquidity: u64,
    #[pyo3(get)]
    pub holders: u64,
    #[pyo3(get)]
    pub whale_metrics: u64,
    #[pyo3(get)]
    pub price: f64,
    #[pyo3(get)]
    pub last_update: u64,
}

#[pymethods]
impl PoolMetrics {
    #[new]
    fn new(
        address: String,
        base_supply: u64,
        quote_supply: u64,
        volume_24h: u64,
        liquidity: u64,
        holders: u64,
        whale_metrics: u64,
        price: f64,
        last_update: u64,
    ) -> Self {
        Self {
            address,
            base_supply,
            quote_supply,
            volume_24h,
            liquidity,
            holders,
            whale_metrics,
            price,
            last_update,
        }
    }
}

#[pyclass]
pub struct TradingCore {
    rpc_client: RpcClient,
    keypair: Keypair,
    commitment: CommitmentConfig,
    pool_metrics: HashMap<String, PoolMetrics>,
    active_pool: Option<String>,
    last_trade_amount: Option<u64>,
    last_trade_out: Option<u64>,
    last_price: Option<f64>,
    last_trade_time: Option<u64>,
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(secret_key: String, _rpc_url: String) -> PyResult<Self> {
        let decoded_key = bs58::decode(&secret_key)
            .into_vec()
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?;

        let keypair = Keypair::from_bytes(&decoded_key)
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?;

        let rpc_client = RpcClient::new(MARGA_RPC_URL.to_string());

        Ok(Self {
            rpc_client,
            keypair,
            commitment: CommitmentConfig::confirmed(),
            pool_metrics: HashMap::new(),
            context of AI assistants, is a data structure or object that holds information about various trading pools or markets. It is used to store and retrieve data related to these pools, such as their addresses, supply, liquidity, and other relevant metrics. The AI assistant can use this information to make informed decisions about trading strategies, risk management, and market analysis.
            commitment,
            pool_metrics: HashMap::new(),
            active_pool: None,
            last_trade_amount: None,
            last_trade_out: None,
            last_price: None,
            last_trade_time: None,
        })
    }

fn get_priority_fee(&self, account_keys: Vec<String>) -> PyResult<u64> {
        let runtime = Runtime::new()
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?;

        let request = PriorityFeeRequest {
            jsonrpc: "2.0".to_string(),
            id: "quantum-trading".to_string(),
            method: "getPriorityFeeEstimate".to_string(),
            params: vec![PriorityFeeParams {
                account_keys,
                options: PriorityFeeOptions {
                    priority_level: "HIGH".to_string(),
                    include_all_priority_fee_levels: false,
                    recommended: true,
                },
            }],
        };

        let client = reqwest::Client::new();
        let response = runtime.block_on(async {
            client.post(MARGA_RPC_URL)
                .json(&request)
                .send()
                .await
                .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?
                .json::<PriorityFeeResponse>()
                .await
                .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?
                .result
                .priority_fee_estimate as u64
        })
    }

fn create_swap_ix(&self, amm_id: &str, amount_in: u64, min_out: u64) -> Result<Vec<u8>, String> {
        let amm_pubkey = Pubkey::from_str(amm_id)
            .map_err(|e| format!("Invalid AMM pubkey: {}", e))?;

        let token_program_id = Pubkey::from_str(TOKEN_PROGRAM_ID)
            .map_err(|e| format!("Invalid token program ID: {}", e))?;

        let raydium_program_id = Pubkey::from_str(RAYDIUM_PROGRAM_ID)
    }
fn create_swap_ix(&self, amm_id: &str, amount_in: u64, min_out: u64) -> Result<Vec<u8>, String> {
    let amm_pubkey = Pubkey::from_str(amm_id)
        .map_err(|e| format!("Invalid AMM pubkey: {}", e))?;

    let token_program_id = Pubkey::from_str(TOKEN_PROGRAM_ID)
        .map_err(|e| format!("Invalid token program ID: {}", e))?;

    let raydium_program_id = Pubkey::from_str(RAYDIUM_PROGRAM_ID)
        .map_err(|e| format!("Invalid Raydium program ID: {}", e))?;

    let accounts = vec![
        AccountMeta::new(amm_pubkey, false),
        AccountMeta::new(self.keypair.pubkey(), true),
        AccountMeta::new_readonly(system_program::id(), false),
        AccountMeta::new_readonly(token_program_id, false),
    ];

    let mut data = vec![2];
    data.extend_from_slice(&amount_in.to_le_bytes());
    data.extend_from_slice(&min_out.to_le_bytes());

    Ok(data)
}

fn execute_trade(&self, params_str: String) -> PyResult<String> {
    let params: serde_json::Value = serde_json::from_str(&params_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?;

    let amm_id = params["pool_address"].as_str().unwrap_or_default();
    let amount_in = params["amount_in"].as_u64().unwrap_or(0);
    let min_out = params["min_amount_out"].as_u64().unwrap_or(0);

    let account_keys = vec![
        amm_id.to_string(),
        self.keypair.pubkey().to_string(),
        system_program::id().to_string(),
    ];

    let priority_fee = self.get_priority_fee(account_keys)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?;

    let swap_ix = self.create_swap_ix(amm_id, amount_in, min_out)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?;

    let instructions = vec![
        ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_BUDGET),
        ComputeBudgetInstruction::set_compute_unit_price(priority_fee),
        swap_ix,
    ];

    let blockhash = self.rpc_client.get_latest_blockhash()
        .await
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?;

    let message = Message::new_with_blockhash(&instructions, Some(&self.keypair.pubkey()), &blockhash);

    let mut transaction = system_transaction::Transaction::new(&[&self.keypair], message, blockhash);

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

    let signature = self.rpc_client.send_transaction_with_config(&transaction, config)
        .await
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?;

    Ok(signature.to_string())
}

fn get_balance(&self) -> PyResult<f64> {
    let balance = self.rpc_client.get_balance(&self.keypair.pubkey())
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?;

    Ok(balance as f64 / SCALING_FACTOR as f64)
}

fn get_active_trades(&self) -> PyResult<String> {
    let trades = json!({
        "pool_address": self.active_pool.clone().unwrap_or_default(),
        "amount_in": self.last_trade_amount.unwrap_or(0) as f64 / SCALING_FACTOR as f64,
        "amount_out": self.last_trade_out.unwrap_or(0) as f64 / SCALING_FACTOR as f64,
        "entry_price": self.last_price.unwrap_or(0.0),
        "entry_time": self.last_trade_time.unwrap_or(0),
    });

    Ok(trades.to_string())
}

fn get_holder_info(&self, token_mint: String) -> PyResult<String> {
    let token_pubkey = Pubkey::from_str(&token_mint)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?;

    let accounts = self.rpc_client.get_token_largest_accounts(&token_pubkey)
        .await
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?;

    let holders: HashMap<String, u64> = accounts.into_iter()
        .map(|acc| {
            let ui_amount = acc.amount.ui_amount.unwrap_or(0.0);
            let amount = (ui_amount * SCALING_FACTOR as f64) as u64;
            (acc.address.to_string(), amount)
        })
        .collect();

    Ok(serde_json::to_string(&holders)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?)
}

fn update_pool_metrics(&mut self, pool_address: String, metrics: PoolMetrics) {
    self.pool_metrics.insert(pool_address, metrics);
}

fn get_pool_metrics(&self, pool_address: String) -> PyResult<String> {
    if let Some(metrics) = self.pool_metrics.get(&pool_address) {
        Ok(serde_json::to_string(metrics)
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError>(e))?)
    } else {
        Err(PyErr::new::<pyo3::exceptions::PyValueError, _>("Pool not found"))
    }
}

fn parse_pool_data(&self, data: &[u8]) -> PoolMetrics {
    let base_supply = u64::from_le_bytes(data[8..16].try_into().unwrap_or([0; 8]));
    let quote_supply = u64::from_le_bytes(data[16..24].try_into().unwrap_or([0; 8]));
    let holders = u64::from_le_bytes(data[32..40].try_into().unwrap_or([0; 8]));
    let whale_metrics = u64::from_le_bytes(data[48..56].try_into().unwrap_or([0; 8]));
    let volume_24h = u64::from_le_bytes(data[112..120].try_into().unwrap_or([0; 8]));
    let liquidity = u64::from_le_bytes(data[128..136].try_into().unwrap_or([0; 8]));

    let price = if base_supply > 0 {
        (quote_supply as f64) / (base_supply as f64)
    } else {
        0.0
    };

    PoolMetrics::new(
        String::new(),
        base_supply,
        quote_supply,
        volume_24h,
        liquidity,
        holders,
        whale_metrics,
        price,
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs(),
    )
}

fn py_create_swap_instruction(&self, amm_id: String, amount_in: f64, min_out: f64) -> PyResult<bool> {
    let amount_in_lamports = (amount_in * SCALING_FACTOR as f64) as u64;
    let min_out_lamports = (min_out * SCALING_FACTOR as f64) as u64;

    self.create_swap_ix(&amm_id, amount_in_lamports, min_out_lamports)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError>(e))?;

    Ok(true)
}
#[pymodule]
fn solana_rust_bot(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_class::<TradingCore>()?;
    Ok(())
}
 please fix the spacing on this script and write it all out for me imports alll the way down every line i need the code fully