use pyo3::prelude::*;
use pyo3::exceptions::PyRuntimeError;
use pyo3_asyncio::tokio::future_into_py;

#[allow(unused_imports)]
use futures_util::{SinkExt, StreamExt};
use log::{error, info};
use serde::{Deserialize, Serialize};
use serde_json::json;
use solana_client::{
    client_error::ClientError,
    nonblocking::rpc_client::RpcClient,
    rpc_config::RpcSendTransactionConfig,
};
use solana_sdk::{
    commitment_config::CommitmentConfig,
    compute_budget::ComputeBudgetInstruction,
    instruction::{AccountMeta, Instruction},
    message::Message,
    pubkey::Pubkey,
    signature::Keypair,
    signer::Signer,
    system_instruction,
};
use std::{
    fmt,
    str::FromStr,
    sync::Arc,
    sync::atomic::{AtomicU64, Ordering},
    time::{Duration, Instant, SystemTime},
};
use tokio::{
    sync::{broadcast, RwLock},
    time::sleep,
};
use dashmap::DashMap;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;

// Constants
const RAYDIUM_V4_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const WSOL_MINT: &str = "So1111111111111111111111111111111111111112";
const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const SYSTEM_PROGRAM_ID: &str = "11111111111111111111111111111111";
const WS_RECONNECT_DELAY: u64 = 1000;
const MAX_RETRIES: u32 = 3;

// AMM field positions
const CONFIRMED_POSITIONS: [(usize, usize); 7] = [
    (8, 16),    // BASE_SUPPLY
    (16, 24),   // QUOTE_SUPPLY
    (112, 120), // VOLUME_24H
    (128, 136), // LIQUIDITY
    (520, 528), // FEES
    (32, 40),   // HOLDERS
    (48, 56),   // WHALE_METRICS
];

#[derive(Debug, Serialize, Deserialize, Clone)]
struct PoolMetrics {
    token_mint: String,
    liquidity: f64,
    volume_5min: f64,
    volume_24h: f64,
    price: f64,
    buy_pressure: u32,
    sell_pressure: u32,
    market_cap: f64,
    bs_ratio: f64,
    lp_locked_percent: f64,
    price_impact: Option<f64>,
    predicted_price: Option<f64>,
    execution_probability: Option<f64>,
    last_update: u64, // Converted to Unix timestamp in seconds
    holders: Option<u32>,
    mint_authority: Option<String>,
    total_supply: Option<u64>,
    base_reserve: u64,
    quote_reserve: u64,
}

#[derive(Debug, Serialize, Deserialize)]
struct TPSMetrics {
    current_tps: f64,
    target_tps: f64,
    success_rate: f64,
    avg_execution_time: f64,
    consecutive_successes: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[pyclass]
pub struct TradeInstruction {
    #[pyo3(get, set)]
    pub amm_id: String,
    #[pyo3(get, set)]
    pub pool_authority: String,
    #[pyo3(get, set)]
    pub base_vault: String,
    #[pyo3(get, set)]
    pub quote_vault: String,
    #[pyo3(get, set)]
    pub amount_in: u64,
    #[pyo3(get, set)]
    pub min_amount_out: u64,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub priority_fee: Option<u64>,
}

#[derive(Debug)]
struct BotError(String);

impl fmt::Display for BotError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }

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

impl From<BotError> for PyErr {
    fn from(err: BotError) -> PyErr {
        PyErr::new::<pyo3::exceptions::PyValueError, _>(err.0)
    }
}

#[pyclass]
#[derive(Clone)]
pub struct TradingCore {
    rpc_client: Arc<RpcClient>,
    websocket_url: String,
    keypair: Arc<Keypair>,
    commitment: CommitmentConfig,
    pool_metrics: Arc<DashMap<String, PoolMetrics>>,
    priority_fee: u64,
    compute_limit: u32,
    ws_tx: broadcast::Sender<WsMessage>,
    ws_connected: Arc<RwLock<bool>>,
    last_trade_time: Arc<RwLock<Instant>>,
    current_tps: Arc<AtomicU64>,
    consecutive_successes: Arc<AtomicU64>,
}

impl TradingCore {
    async fn decode_pool_data(&self, data: &[u8]) -> Result<Option<PoolMetrics>, BotError> {
        if data.len() < 528 {
            // Minimum size needed for all fields
            return Ok(None);
        }

        let base_reserve = u64::from_le_bytes(data[8..16].try_into().unwrap());
        let quote_reserve = u64::from_le_bytes(data[16..24].try_into().unwrap());
        let volume_24h = u64::from_le_bytes(data[112..120].try_into().unwrap()) as f64 / 1e6;
        let liquidity = u64::from_le_bytes(data[128..136].try_into().unwrap()) as f64 / 1e6;
        let holders = u32::from_le_bytes(data[32..36].try_into().unwrap());

        // Calculate price and other metrics
        let price = if base_reserve > 0 {
            (quote_reserve as f64) / (base_reserve as f64)
        } else {
            0.0
        };

        let buy_pressure = u32::from_le_bytes(data[88..92].try_into().unwrap());
        let sell_pressure = u32::from_le_bytes(data[92..96].try_into().unwrap());
        let volume_5min = u64::from_le_bytes(data[96..104].try_into().unwrap()) as f64 / 1e6;
        let market_cap = u64::from_le_bytes(data[104..112].try_into().unwrap()) as f64 / 1e6;

        let bs_ratio = if sell_pressure > 0 {
            buy_pressure as f64 / sell_pressure as f64
        } else {
            buy_pressure as f64
        };

        Ok(Some(PoolMetrics {
            token_mint: hex::encode(&data[32..64]),
            liquidity,
            volume_5min,
            volume_24h,
            price,
            buy_pressure,
            sell_pressure,
            market_cap,
            bs_ratio,
            lp_locked_percent: 100.0,
            price_impact: None,
            predicted_price: None,
            execution_probability: None,
            last_update: SystemTime::now()
                .duration_since(SystemTime::UNIX_EPOCH)
                .unwrap()
                .as_secs(),
            holders: Some(holders),
            mint_authority: None,
            total_supply: None,
            base_reserve,
            quote_reserve,
        }))
    }

    async fn process_ws_message(&self, message: &str) -> Result<(), BotError> {
        let data: serde_json::Value =
        serde_json::from_str(message).map_err(|e| BotError(e.to_string()))?;

    if let Some(params) = data.get("params") {
        if let Some(result) = params.get("result") {
            if let Some(value) = result.get("value") {
                if let Some(data) = value.get("data") {
                    let decoded = BASE64_STANDARD
                        .decode(data.as_str().unwrap_or(""))
                        .map_err(|e| BotError(e.to_string()))?;

                    if let Some(metrics) = self.decode_pool_data(&decoded).await? {
                        self.pool_metrics.insert(metrics.token_mint.clone(), metrics);
                    }
                }
            }
        }
    }

    Ok(())
}

    async fn update_tps(&self, success: bool, execution_time: f64) {
        let current = self.current_tps.load(Ordering::Relaxed);

        if success {
            let successes = self.consecutive_successes.fetch_add(1, Ordering::Relaxed);
            if successes > 100 && execution_time < 0.7 {
                // Increase TPS if performance is good
                let new_tps = by a json object: {
                    "current_tps": current + 1,
                    "target_tps": current + 1,
                    "success_rate": 1.0,
                    "avg_execution_time": execution_time,
                    "consecutive_successes": successes,
                };
                self.current_tps.store(new_tps, Ordering::Relaxed);
            }
        } else {
            self.consecutive_successes.store(0, Ordering::Relaxed);
            // Decrease TPS on failure
            let new_tps = current.saturating_sub(2);
            self.current_tps.store(new_tps.max(1), Ordering::Relaxed);
        }
    }

    pub async fn execute_trade_instruction(&self, instruction: &TradeInstruction) -> Result<String, BotError> {
        let start_time = Instant::now();

        // Sleep based on current TPS
        let current_tps = self.current_tps.load(Ordering::Relaxed);
        if current_tps > 0 {
            sleep(Duration::from_secs_f64(1.0 / current_tps)).await;
        }

        let blockhash = self
            .rpc_client
            .get_latest_blockhash()
            .await
            .map_err(|e| BotError(e.to_string()))?;

        let priority_fee = instruction.priority_fee.unwrap_or(self.priority_fee);

        let mut instructions = vec![
            ComputeBudgetInstruction::set_compute_unit_limit(self.compute_limit),
            ComputeBudgetInstruction::set_compute_unit_price(priority_fee),
        ];

        let swap_ix = self.create_swap_instruction(instruction)?;
        instructions.push(swap_ix);

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

        let mut transaction = solana_sdk::transaction::Transaction::new_unsigned(message);
        transaction.sign(&[&self.keypair], blockhash);

        let config = RpcSendTransactionConfig {
            skip_preflight: true,
            preflight_commitment: Some(self.commitment),
            encoding: Some(UiMessageEncoding::Base64),
            max_retries: Some(MAX_RETRIES),
            min_context_slot: None,
        };

        match self
            .rpc_client
            .send_transaction_with_config(&transaction, config)
            .await
        {
            Ok(signature) => {
                let execution_time = start_time.elapsed().as_secs_f64();
                self.update_tps(true, execution_time).await;
                Ok(signature.to_string())
            }
            Err(e) => {
                self.update_tps(false, 0.0).await;
                Err(BotError(e.to_string()))
            }
        }
    }

    fn create_swap_instruction(&self, instruction: &TradeInstruction) -> Result<Instruction, BotError> {
        // Construct the swap instruction based on the TradeInstruction
        let program_id = Pubkey::from_str(RAYDIUM_V4_PROGRAM_ID).map_err(|e| BotError(e.to_string()))?;
        let amm_id = Pubkey::from_str(&instruction.amm_id).map_err(|e| BotError(e.to_string()))?;
        let pool_authority = Pubkey::from_str(&instruction.pool_authority).map_err(|e| BotError(e.to_string()))?;
        let base_vault = Pubkey::from_str(&instruction.base_vault).map_err(|e| BotError(e.to_string()))?;
        let quote_vault = Pubkey::from_str(&instruction.quote_vault).map_err(|e| BotError(e.to_string()))?;
        let user_source = self.keypair.pubkey();
        let user_destination = self.keypair.pubkey();
        let system_program = Pubkey::from_str(SYSTEM_PROGRAM_ID).unwrap();
        let token_program = Pubkey::from_str(TOKEN_PROGRAM_ID).unwrap();

        let accounts = vec![
            AccountMeta::new_readonly(amm_id, false),
            AccountMeta::new_readonly(pool_authority, false),
            AccountMeta::new(base_vault, false),
            AccountMeta::new(quote_vault, false),
            AccountMeta::new(user_source, true),
            AccountMeta::new(user_destination, false),
            AccountMeta::new_readonly(system_program, false),
            AccountMeta::new_readonly(token_program, false),
        ];

        let data = vec![]; // Populate with actual instruction data

        Ok(Instruction {
            program_id,
            accounts,
            data,
        })
    }
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(rpc_url: String, secret_key_b58: String) -> PyResult<Self> {
        // Initialize keypair from base58 string
        let secret_key = bs58::decode(secret_key_b58)
            .into_vec()
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;

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

        // Initialize RPC client with retry and timeout configuration
        let rpc_client = Arc::new(RpcClient::new_with_timeout(
            rpc_url,
            Duration::from_secs(30),
            CommitmentConfig::Confirmed,
        ));

        let (ws_tx, _) = broadcast::channel(100);

        Ok(Self {
            rpc_client,
            websocket_url: "wss://marga-e5m84-fast-mainnet.helius-rpc.com/".to_string(),
            keypair: Arc::new(keypair),
            commitment: CommitmentConfig::Confirmed,
            pool_metrics: Arc::new(DashMap::new()),
            priority_fee: 65_000,
            compute_limit: 1_400_000,
            ws_tx,
            ws_connected: Arc::new(RwLock::new(false)),
            last_trade_time: Arc::new(RwLock::new(Instant::now())),
            current_tps: Arc::new(AtomicU64::new(1)),
            consecutive_successes: Arc::new(AtomicU64::new(0)),
        })
    }

    #[pyo3(name = "execute_trade")]
    fn execute_trade<'a>(&self, py: Python<'a>, instruction: TradeInstruction) -> PyResult<&'a PyAny> {
        let trading_core = self.clone();
        future_into_py(py, async move {
            trading_core
                .execute_trade_instruction(&instruction)
                .await
                .map_err(|e| PyRuntimeError::new_err(e.to_string()))
        })
    }

    #[pyo3(name = "get_pool_metrics")]
    fn get_pool_metrics(&self, token_mint: String) -> PyResult<Option<PoolMetrics>> {
        Ok(self.pool_metrics.get(&token_mint).map(|entry| entry.value().clone()))
    }
}

#[pyclass]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PoolMetrics {
    #[pyo3(get, set)]
    pub token_mint: String,
    #[pyo3(get, set)]
    pub liquidity: f64,
    #[pyo3(get, set)]
    pub volume_5min: f64,
    #[pyo3(get, set)]
    pub volume_24h: f64,
    #[pyo3(get, set)]
    pub price: f64,
    #[pyo3(get, set)]
    pub buy_pressure: u32,
    #[pyo3(get, set)]
    pub sell_pressure: u32,
    #[pyo3(get, set)]
    pub market_cap: f64,
    #[pyo3(get, set)]
    pub bs_ratio: f64,
    #[pyo3(get, set)]
    pub lp_locked_percent: f64,
    #[pyo3(get, set)]
    pub price_impact: Option<f64>,
    #[pyo3(get, set)]
    pub predicted_price: Option<f64>,
    #[pyo3(get, set)]
    pub execution_probability: Option<f64>,
    #[pyo3(get, set)]
    pub last_update: u64,
    #[pyo3(get, set)]
    pub holders: Option<u32>,
    #[pyo3(get, set)]
    pub mint_authority: Option<String>,
    #[pyo3(get, set)]
    pub total_supply: Option<u64>,
    #[pyo3(get, set)]
    pub base_reserve: u64,
    #[pyo3(get, set)]
    pub quote_reserve: u64,
}

#[pymethods]
impl PoolMetrics {
    #[new]
    fn new(
        token_mint: String,
        liquidity: f64,
        volume_5min: f64,
        volume_24h: f64,
        price: f64,
        buy_pressure: u32,
        sell_pressure: u32,
        market_cap: f64,
        bs_ratio: f64,
        lp_locked_percent: f64,
        price_impact: Option<f64>,
        predicted_price: Option<f64>,
        execution_probability: Option<f64>,
        last_update: u64,
        holders: Option<u32>,
        mint_authority: Option<String>,
        total_supply: Option<u64>,
        base_reserve: u64,
        quote_reserve: u64,
    ) -> Self {
        Self {
            token_mint,
            liquidity,
            volume_5min,
            volume_24h,
            price,
            buy_pressure,
            sell_pressure,
            market_cap,
            bs_ratio,
            lp_locked_percent,
            price_impact,
            predicted_price,
            execution_probability,
            last_update,
            holders,
            mint_authority,
            total_supply,
            base_reserve,
            quote_reserve,
        }
    }
}

#[pymethods]
impl TradeInstruction {
    #[new]
    fn new(
        amm_id: String,
        pool_authority: String,
        base_vault: String,
        quote_vault: String,
        amount_in: u64,
        min_amount_out: u64,
        is_buy: bool,
        priority_fee: Option<u64>,
    ) -> Self {
        Self {
            amm_id,
            pool_authority,
            base_vault,
            quote_vault,
            amount_in,
            min_amount_out,
            is_buy,
            priority_fee,
        }
    }
}