use anyhow::{anyhow, Result};

use base64;

use bincode;

use config::{Config, File};

use ctrlc;

use env_logger::Env;

use futures_util::{SinkExt, StreamExt};

use log::{debug, error, info, warn};

use reqwest::Client as HttpClient;

use serde::{Deserialize, Serialize};

use serde_json::json;

use solana_client::rpc_client::RpcClient;

use solana_sdk::{
    commitment_config::CommitmentConfig,
    hash::Hash,
    signer::{keypair::Keypair, Signer},
    transaction::Transaction,
};

use std::{
    collections::HashMap,
    fs::OpenOptions,
    io::{Read, Write},
    sync::{Arc, Mutex as StdMutex},
    time::{Duration, Instant},
};

use tokio::{sync::Mutex, time::interval};
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message as WsMessage};

// Constants
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";
const RAYDIUM_AMM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const MAX_ACTIVE_TRADES: usize = 5;
const BUY_ATTEMPT_DELAY: Duration = Duration::from_millis(200);
const STATE_FILE: &str = "bot_state.json";
const PRICE_CHECK_INTERVAL: Duration = Duration::from_secs(1);
const WEBSOCKET_PING_INTERVAL: Duration = Duration::from_secs(285); // Ping every 4 minutes and 45 seconds

// Hardcoded Keypair and Config Paths
const KEYPAIR_PATH: &str = "C:\\solana_rust_bot\\id.json";
const CONFIG_PATH: &str = "C:\\solana_rust_bot\\config.toml";

// Hardcoded Trading Parameters
const SNIPE_AMOUNT: f64 = 0.005; // SOL
const PROFIT_TARGET_PERCENT: f64 = 30.0; // 30%
const MAX_RETRY_ATTEMPTS: u32 = 3;
const STOP_LOSS_PERCENT: f64 = 5.0; // 5%

#[derive(Debug, Deserialize, Clone)]
pub struct BotConfig {
    rpc_url: String,
    helius_websocket_url: String,
    helius_api_key: String,
    max_slippage: f64,
    priority_fee: u64,
    min_liquidity: f64,
    max_liquidity: f64,
    min_sol_balance: f64,
}

fn load_config() -> Result<BotConfig> {
    let settings = Config::builder()
        .add_source(File::with_name(CONFIG_PATH))
        .build()?;
    let mut config: BotConfig = settings.try_deserialize()?;
    config.max_slippage = config.max_slippage;
    Ok(config)
}

fn load_keypair() -> Result<Keypair> {
    let data = std::fs::read(KEYPAIR_PATH)?;
    let json: Vec<u8> = serde_json::from_slice(&data)?;
    let keypair = Keypair::from_bytes(&json)?;
    Ok(keypair)
}

#[derive(Clone, Serialize, Deserialize, Debug)]
struct Trade {
    token_address: String,
    amount: u64,
    buy_price: f64,
    take_profit_price: f64,
    stop_loss_price: f64,
    timestamp: u64,
}

struct PriceCache {
    prices: HashMap<String, (f64, Instant)>,
    ttl: Duration,
}

impl PriceCache {
    fn new(ttl: u64) -> Self {
        Self {
            prices: HashMap::new(),
            ttl: Duration::from_secs(ttl),
        }
    }

    fn get(&self, token_address: &str) -> Option<f64> {
        self.prices
            .get(token_address)
            .and_then(|(price, timestamp)| {
                if timestamp.elapsed() < self.ttl {
                    Some(*price)
                } else {
                    None
                }
            })
    }

    fn set(&mut self, token_address: String, price: f64) {
        self.prices
            .insert(token_address.clone(), (price, Instant::now()));
        debug!("Price cache updated: {} = {}", token_address, price);
    }
}

struct Bot {
    config: BotConfig,
    rpc_client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    price_cache: Arc<Mutex<PriceCache>>,
    active_trades: Arc<Mutex<HashMap<String, Trade>>>,
    last_blockhash: Arc<Mutex<(Hash, Instant)>>,
    swap_limiter: Arc<Mutex<()>>,
    http_client: HttpClient,
}

impl Bot {
    async fn new() -> Result<Self> {
        info!("Loading configuration from environment file");
        let config = load_config()?;
        info!("Config loaded: {:?}", config);

        let rpc_client = Arc::new(RpcClient::new_with_commitment(
            config.rpc_url.clone(),
            CommitmentConfig::processed(),
        ));
        let http_client = HttpClient::builder()
            .timeout(Duration::from_secs(10))
            .build()?;
        let keypair = Arc::new(load_keypair()?);
        debug!("Keypair successfully loaded.");

        let bot = Self {
            config,
            rpc_client,
            keypair,
            price_cache: Arc::new(Mutex::new(PriceCache::new(60))),
            active_trades: Arc::new(Mutex::new(HashMap::new())),
            last_blockhash: Arc::new(Mutex::new((Hash::default(), Instant::now()))),
            swap_limiter: Arc::new(Mutex::new(())),
            http_client,
        };
        bot.load_state().await?;
        info!("Bot initialized successfully.");
        Ok(bot)
    }

    async fn run(&self) -> Result<()> {
        info!("Starting Raydium Sniper Bot");

        let initial_balance = self.get_sol_balance().await?;
        info!("Initial SOL balance: {} SOL", initial_balance as f64 / 1e9);

        loop {
            info!("Attempting to connect to Helius WebSocket...");
            match connect_async(&self.config.helius_websocket_url).await {
                Ok((ws_stream, _)) => {
                    let (mut write, mut read) = ws_stream.split();
                    info!("Connected to Helius WebSocket");

                    write
                        .send(WsMessage::Text(
                            json!({
                                "jsonrpc": "2.0",
                                "id": 1,
                                "method": "logsSubscribe",
                                "params": {
                                    "filter": {
                                        "mentions": [RAYDIUM_AMM_PROGRAM_ID]
                                    },
                                    "commitment": "confirmed"
                                }
                            })
                            .to_string(),
                        ))
                        .await?;
                    info!("Subscribed to Raydium AMM logs");

                    let mut price_check_interval = interval(PRICE_CHECK_INTERVAL);
                    let mut websocket_ping_interval = interval(WEBSOCKET_PING_INTERVAL);

                    loop {
                        tokio::select! {
                            Some(msg) = read.next() => {
                                match msg {
                                    Ok(WsMessage::Text(text)) => {
                                        debug!("Received WebSocket message: {}", text);
                                        if let Err(e) = self.handle_event(&text).await {
                                            error!("Error handling event: {}", e);
                                        }
                                    }
                                    Ok(WsMessage::Close(_)) => {
                                        warn!("WebSocket connection closed");
                                        break;
                                    }
                                    Ok(_) => {},
                                    Err(e) => {
                                        error!("WebSocket error: {}", e);
                                        break;
                                    }
                                }
                            }
                            _ = price_check_interval.tick(), if !self.active_trades.lock().await.is_empty() => {
                                if let Err(e) = self.check_active_trades().await {
                                    error!("Error checking active trades: {}", e);
                                }
                            }
                            _ = websocket_ping_interval.tick() => {
                                info!("Sending WebSocket ping to keep connection alive");
                                if let Err(e) = write.send(WsMessage::Ping(Vec::new())).await {
                                    error!("Error sending WebSocket ping: {}", e);
                                    break;
                                }
                            }
                        }
                    }
                }
                Err(e) => {
                    error!("WebSocket connect error: {}. Retrying in 5 seconds...", e);
                    tokio::time::sleep(Duration::from_secs(5)).await;
                }
            }
        }
    }

    async fn handle_event(&self, text: &str) -> Result<()> {
        debug!("Handling event for message: {}", text);
        let event: serde_json::Value = serde_json::from_str(text)?;

        if let Some(error) = event.get("error") {
            return Err(anyhow!("WebSocket error received: {}", error));
        }

        if let Some(params) = event.get("params") {
            if let Some(result) = params.get("result") {
                // Extract the program ID from the logs
                if let Some(logs) = result.get("logs").and_then(|v| v.as_array()) {
                    for log in logs {
                        if let Some(log_str) = log.as_str() {
                            if log_str.contains(RAYDIUM_AMM_PROGRAM_ID) {
                                if let Some(token_address) = extract_token_address(log_str) {
                                    info!("New pool detected: Token {}", token_address);
                                    self.process_token(token_address).await?;
                                }
                            }
                        }
                    }
                }
            }
        }
        Ok(())
    }

    async fn process_token(&self, token_address: String) -> Result<()> {
        let metadata = self.get_token_metadata(vec![token_address.clone()]).await?;
        if let Some(meta) = metadata.get(&token_address) {
            if self.is_valid_pool(&token_address, meta).await? {
                info!("Token {} is valid, attempting to fetch liquidity");

    async fn get_token_metadata(
        &self,
        token_addresses: Vec<String>,
    ) -> Result<HashMap<String, serde_json::Value>> {
        let config = load_config()?;
        let url = format!(
            "https://api.helius.xyz/v0/token-metadata?api-key={}",
            config.helius_api_key
        );
    
        let body = json!({
            "jsonrpc": "2.0",
            "id": "get-token-metadata",
            "method": "getAsset",
            "params": [
                token_addresses,
                {
                    "encoding": "jsonParsed"
                }
            ]
        });
    
        info!("Fetching metadata from URL: {}", url);
    
        let response = self
            .http_client
            .post(&url)
            .json(&body)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;
    
        // Process the response and return the metadata
        let metadata_map = response["result"]
            .as_array()
            .ok_or_else(|| anyhow!("Invalid metadata response format"))?
            .iter()
            .map(|meta| {
                let address = meta["id"].as_str().unwrap_or("").to_string();
                (address, meta.clone())
            })
            .collect();
    
        debug!("Metadata response for multiple tokens: {:?}", metadata_map);
    
        Ok(metadata_map)
    }
    

    async fn is_valid_pool(
        &self,
        token_address: &str,
        metadata: &serde_json::Value,
    ) -> Result<bool> {
        debug!("Validating pool for token: {}", token_address);

        let is_raydium_pool = metadata
            .get("updateAuthority")
            .and_then(serde_json::Value::as_str)
            .map_or(false, |auth| auth == RAYDIUM_AMM_PROGRAM_ID);
        
        let is_spl_token = metadata
            .get("tokenStandard")
            .and_then(serde_json::Value::as_str)
            == Some("SPL");

        // Removed mint_authority check; replacing with burnt == true
        let burnt = metadata
            .get("burnt")
            .and_then(serde_json::Value::as_bool)
            .unwrap_or(false);

        let liquidity = metadata
            .get("liquidity")
            .and_then(serde_json::Value::as_f64)
            .unwrap_or(0.0);

        let is_valid = is_raydium_pool
            && is_spl_token
            && burnt
            && liquidity >= self.config.min_liquidity
            && liquidity <= self.config.max_liquidity;

        info!(
            "Pool validation for {}: Raydium: {}, SPL: {}, Burnt: {}, Liquidity: {} SOL, Valid: {}",
            token_address,
            is_raydium_pool,
            is_spl_token,
            burnt,
            liquidity,
            is_valid
        );

        Ok(is_valid)
    }

    async fn attempt_buy(&self, token_address: String) -> Result<()> {
        info!("Starting buy attempts for token: {}", token_address);
        let active_trades_count = self.active_trades.lock().await.len();
        if active_trades_count >= MAX_ACTIVE_TRADES {
            warn!(
                "Maximum active trades ({}) reached. Skipping buy for {}.",
                MAX_ACTIVE_TRADES, token_address
            );
            return Ok(());
        }

        let snipe_amount = SNIPE_AMOUNT;
        let snipe_amount_lamports = (snipe_amount * 1e9) as u64;

        for attempt in 1..=MAX_RETRY_ATTEMPTS {
            info!("Buy attempt {} for token {}.", attempt, token_address);
            match self
                .execute_buy(&token_address, snipe_amount_lamports)
                .await
            {
                Ok(_) => {
                    info!("Successfully bought token {}", token_address);
                    return Ok(());
                }
                Err(e) => {
                    warn!(
                        "Buy attempt {} failed for token {}: {}",
                        attempt, token_address, e
                    );
                    if attempt < MAX_RETRY_ATTEMPTS {
                        tokio::time::sleep(BUY_ATTEMPT_DELAY).await;
                    }
                }
            }
        }
        error!("All buy attempts failed for token: {}", token_address);
        Ok(())
    }

    async fn execute_buy(&self, token_address: &str, amount: u64) -> Result<()> {
        debug!(
            "Preparing to execute buy for token: {}, amount: {}",
            token_address, amount
        );
        let _guard = self.swap_limiter.lock().await;

        let jupiter_url = "https://quote-api.jup.ag/v6/quote";
        let params = json!({
            "inputMint": WSOL_MINT,
            "outputMint": token_address,
            "amount": amount.to_string(),
            "slippageBps": (self.config.max_slippage * 100.0) as u64,
        });

        info!("Fetching quote for buy: {:?}", params);
        let quote_response = self
            .http_client
            .get(jupiter_url)
            .query(&params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        debug!("Received quote response: {:?}", quote_response);

        let swap_url = "https://quote-api.jup.ag/v6/swap";
        let swap_params = json!({
            "quoteResponse": quote_response,
            "userPublicKey": self.keypair.pubkey().to_string(),
            "wrapUnwrapSOL": true,
            "priorityFee": self.config.priority_fee,
        });

        info!("Initiating swap with parameters: {:?}", swap_params);
        let swap_response = self
            .http_client
            .post(swap_url)
            .json(&swap_params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        debug!("Received swap response: {:?}", swap_response);

        let transaction_data = base64::decode(swap_response["swapTransaction"].as_str().unwrap())?;
        let mut transaction: Transaction = bincode::deserialize(&transaction_data)?;
        let recent_blockhash = self.get_recent_blockhash().await?;
        transaction.sign(&[&self.keypair], recent_blockhash);

        debug!("Sending transaction to Solana network");
        let signature = self.rpc_client.send_and_confirm_transaction(&transaction)?;
        info!("Buy transaction confirmed. Signature: {}", signature);

        // Record the trade
        {
            let mut active_trades = self.active_trades.lock().await;
            let out_amount = quote_response["outAmount"].as_f64().unwrap_or(0.0);
            let trade = Trade {
                token_address: token_address.to_string(),
                amount,
                buy_price: out_amount,
                take_profit_price: out_amount * (1.0 + PROFIT_TARGET_PERCENT / 100.0),
                stop_loss_price: out_amount * (1.0 - STOP_LOSS_PERCENT / 100.0),
                timestamp: Instant::now().elapsed().as_secs(),
            };
            active_trades.insert(token_address.to_string(), trade.clone());
            debug!("Added trade to active trades: {:?}", trade);
        }

        Ok(())
    }

    async fn check_active_trades(&self) -> Result<()> {
        info!("Checking active trades for sell opportunities...");
        let mut trades_to_remove = Vec::new();
        let active_trades = self.active_trades.lock().await;

        for (token_address, trade) in active_trades.iter() {
            let current_price = self.get_token_price(token_address).await?;
            debug!("Current price for {}: {}", token_address, current_price);

            if current_price >= trade.take_profit_price || current_price <= trade.stop_loss_price {
                info!(
                    "Executing sell for token {}. Current price: {}.",
                    token_address, current_price
                );
                if let Err(e) = self.execute_sell(token_address).await {
                    error!("Failed to sell token {}: {}", token_address, e);
                } else {
                    trades_to_remove.push(token_address.clone());
                }
            }
        }
        drop(active_trades);

        if !trades_to_remove.is_empty() {
            let mut active_trades = self.active_trades.lock().await;
            for token_address in trades_to_remove {
                active_trades.remove(&token_address);
                info!(
                    "Removed trade for token {} after successful sell.",
                    token_address
                );
            }
        }
        Ok(())
    }

    async fn execute_sell(&self, token_address: &str) -> Result<()> {
        debug!("Preparing to execute sell for token: {}", token_address);
        let _guard = self.swap_limiter.lock().await;

        let amount = {
            let active_trades = self.active_trades.lock().await;
            active_trades
                .get(token_address)
                .map(|trade| trade.amount)
                .ok_or_else(|| anyhow!("Trade not found for token {}", token_address))?
        };

        let jupiter_url = "https://quote-api.jup.ag/v6/quote";
        let params = json!({
            "inputMint": token_address,
            "outputMint": WSOL_MINT,
            "amount": amount.to_string(),
            "slippageBps": (self.config.max_slippage * 100.0) as u64,
        });

        info!("Fetching quote for sell: {:?}", params);
        let quote_response = self
            .http_client
            .get(jupiter_url)
            .query(&params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        debug!("Received quote response: {:?}", quote_response);

        let swap_url = "https://quote-api.jup.ag/v6/swap";
        let swap_params = json!({
            "quoteResponse": quote_response,
            "userPublicKey": self.keypair.pubkey().to_string(),
            "wrapUnwrapSOL": true,
            "priorityFee": self.config.priority_fee,
        });

        info!("Initiating swap with parameters: {:?}", swap_params);
        let swap_response = self
            .http_client
            .post(swap_url)
            .json(&swap_params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        debug!("Received swap response: {:?}", swap_response);

        let transaction_data = base64::decode(swap_response["swapTransaction"].as_str().unwrap())?;
        let mut transaction: Transaction = bincode::deserialize(&transaction_data)?;
        let recent_blockhash = self.get_recent_blockhash().await?;
        transaction.sign(&[&self.keypair], recent_blockhash);

        debug!("Sending transaction to Solana network");
        let signature = self.rpc_client.send_and_confirm_transaction(&transaction)?;
        info!("Sell transaction confirmed. Signature: {}", signature);

        Ok(())
    }

    async fn get_token_price(&self, token_address: &str) -> Result<f64> {
        debug!("Fetching token price for: {}", token_address);
        if let Some(price) = self.price_cache.lock().await.get(token_address) {
            debug!("Price cache hit for {}: {}", token_address, price);
            return Ok(price);
        }

        let url = format!(
            "https://rpc.helius.xyz?api-key={}",
            self.config.helius_api_key
        );

        let body = json!({
            "jsonrpc": "2.0",
            "id": "get-token-price",
            "method": "getTokenMetadata",
            "params": [
                [token_address]
            ]
        });

        info!("Fetching token price from URL: {}", url);
        let response = self
            .http_client
            .post(&url)
            .json(&body)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;
        debug!("Received token metadata response: {:?}", response);

        if let Some(price) = response["result"][0]["price"].as_f64() {
            self.price_cache
                .lock()
                .await
                .set(token_address.to_string(), price);
            Ok(price)
        } else {
            Err(anyhow!("Price not found for token {}", token_address))
        }
    }

    async fn get_sol_balance(&self) -> Result<u64> {
        debug!(
            "Fetching SOL balance for account: {}",
            self.keypair.pubkey()
        );
        let balance = self.rpc_client.get_balance(&self.keypair.pubkey())?;
        if balance < self.config.min_sol_balance as u64 * 1_000_000_000 {
            warn!(
                "SOL balance is below minimum threshold: {} SOL",
                balance as f64 / 1e9
            );
        }
        Ok(balance)
    }

    async fn get_recent_blockhash(&self) -> Result<Hash> {
        debug!("Fetching recent blockhash...");
        let mut last_blockhash = self.last_blockhash.lock().await;
        if last_blockhash.1.elapsed() > Duration::from_secs(60) {
            let new_blockhash = self.rpc_client.get_latest_blockhash()?;
            *last_blockhash = (new_blockhash, Instant::now());
            debug!("Updated blockhash: {}", new_blockhash);
            Ok(new_blockhash)
        } else {
            debug!("Using cached blockhash");
            Ok(last_blockhash.0)
        }
    }

    async fn save_state(&self) -> Result<()> {
        info!("Saving bot state to file...");
        let active_trades = self.active_trades.lock().await;
        let serialized = serde_json::to_string(&*active_trades)?;
        let mut file = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(STATE_FILE)?;
        file.write_all(serialized.as_bytes())?;
        info!("State successfully saved to {}.", STATE_FILE);
        Ok(())
    }

    async fn load_state(&self) -> Result<()> {
        let state_file_path = STATE_FILE;
        info!("Loading bot state from file: {}", state_file_path);
        match std::fs::File::open(state_file_path) {
            Ok(mut file) => {
                let mut contents = String::new();
                file.read_to_string(&mut contents)?;
                let loaded_trades: HashMap<String, Trade> = serde_json::from_str(&contents)?;
                let mut active_trades = self.active_trades.lock().await;
                *active_trades = loaded_trades;
                info!("Successfully loaded state from {}.", state_file_path);
            }
            Err(e) => {
                warn!(
                    "State file not found: {}. Starting with an empty state. Error: {}",
                    state_file_path, e
                );
            }
        }
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
    info!("🚀 Solana Sniper Bot v1.0 - Launching! 🚀");

    let bot = Arc::new(Bot::new().await?);

    // Save state every 60 seconds
    let bot_clone = bot.clone();
    tokio::spawn(async move {
        loop {
            tokio::time::sleep(Duration::from_secs(60)).await;
            if let Err(e) = bot_clone.save_state().await {
                error!("Failed to save state: {}", e);
            }
        }
    });

    // Setup signal handling for graceful shutdown
    let (tx, rx) = tokio::sync::oneshot::channel();
    let tx = Arc::new(StdMutex::new(Some(tx)));
    let tx_clone = tx.clone();

    ctrlc::set_handler(move || {
        if let Some(tx) = tx_clone.lock().unwrap().take() {
            let _ = tx.send(());
        }
    })
    .expect("Error setting Ctrl+C handler");

    tokio::select! {
        result = bot.run() => {
            if let Err(e) = result {
                error!("Bot encountered an error: {}", e);
            }
        }
        _ = rx => {
            info!("Received shutdown signal. Saving state and exiting...");
            if let Err(e) = bot.save_state().await {
                error!("Error saving state on shutdown: {}", e);
            }
        }
    }

    info!("🛑 Bot shutting down. Goodbye!");
    Ok(())
} please fix entire script please