use std::collections::HashMap;
use std::sync::{Arc, Mutex as StdMutex};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use std::str::FromStr;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::fs;
use anyhow::{anyhow, Result};
use config::Config;
use log::{info, warn, error};
use solana_client::rpc_client::RpcClient;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signer::keypair::Keypair;
use solana_sdk::signer::Signer;
use solana_sdk::transaction::Transaction;
use solana_sdk::message::Message;
use tokio::sync::Mutex;
use tokio::time::{interval, sleep};
use futures_util::{SinkExt, StreamExt};
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message as WsMessage};
use serde::{Serialize, Deserialize};
use bincode::deserialize;
use helius_sdk::Helius as HeliusClient;
use helius_sdk::{self as helius, Client as HeliusClient, TokenMetadataRequest};
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";
const MAX_ACTIVE_TRADES: usize = 5;
const MAX_BUY_ATTEMPTS: u32 = 3;
const BUY_ATTEMPT_DELAY: Duration = Duration::from_millis(200);
const RECONNECT_DELAY: Duration = Duration::from_secs(5);
const PRICE_CHECK_INTERVAL: Duration = Duration::from_secs(1);
const STATE_FILE: &str = "bot_state.json";
const RAYDIUM_AMM_PROGRAM_ID: &str = "RVKd61hk3bsLCfvXGCbqfPv1dHUgAyK1fSRbmcyP2tF";

#[derive(Clone, Serialize, Deserialize)]
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, (price, Instant::now()));
    }
}

struct Bot {
    config: Config,
    rpc_client: RpcClient,
    keypair: Keypair,
    price_cache: Arc<Mutex<PriceCache>>,
    active_trades: Arc<Mutex<HashMap<String, Trade>>>,
    last_blockhash: Arc<Mutex<(Hash, Instant)>>,
    metadata_limiter: Arc<Mutex<()>>,
    swap_limiter: Arc<Mutex<()>>,
    helius_client: HeliusClient,
}

impl Bot {
    async fn new(config_path: &str) -> Result<Self> {
        let config = Config::builder()
            .add_source(config::File::with_name(config_path))
            .build()?;
        let rpc_url = config.get_string("rpc_url")?;
        let rpc_client = RpcClient::new(rpc_url);
        
        let keypair = if let Ok(keypair_58) = config.get_string("keypair_58") {
            Keypair::from_base58_string(&keypair_58)
        } else if let Ok(keypair_json_path) = config.get_string("keypair_json") {
            let json_content = fs::read_to_string(keypair_json_path)?;
            let secret_key: Vec<u8> = serde_json::from_str(&json_content)?;
            Keypair::from_bytes(&secret_key)?
        } else if let Ok(keypair_64) = config.get_string("keypair_64") {
            let bytes: Vec<u8> = keypair_64.split(',')
                .map(|s| s.trim().parse::<u8>())
                .collect::<std::result::Result<_, _>>()?;
            Keypair::from_bytes(&bytes)?
        } else {
            return Err(anyhow!("No valid keypair found in config"));
        };

        let price_cache = Arc::new(Mutex::new(PriceCache::new(60)));
        let active_trades = Arc::new(Mutex::new(HashMap::new()));
        let last_blockhash = Arc::new(Mutex::new((Hash::default(), Instant::now())));
        let metadata_limiter = Arc::new(Mutex::new(()));
        let swap_limiter = Arc::new(Mutex::new(()));
        
        let helius_api_key = config.get_string("helius_api_key")?;
        let helius_client = HeliusClient::new(&helius_api_key);

        let mut bot = Self {
            config,
            rpc_client,
            keypair,
            price_cache,
            active_trades,
            last_blockhash,
            metadata_limiter,
            swap_limiter,
            helius_client,
        };

        bot.load_state().await?;

        Ok(bot)
    }

    async fn run(&self) -> Result<()> {
        loop {
            match self.connect_and_listen().await {
                Ok(_) => {
                    error!("WebSocket connection closed unexpectedly");
                }
                Err(e) => {
                    error!("Error in WebSocket connection: {}", e);
                }
            }
            info!("Attempting to reconnect in {} seconds", RECONNECT_DELAY.as_secs());
            sleep(RECONNECT_DELAY).await;
        }
    }

    async fn connect_and_listen(&self) -> Result<()> {
        let (ws_stream, _) = connect_async(&self.config.get_string("helius_websocket_url")?).await?;
        let (mut write, mut read) = ws_stream.split();
        
        write.send(WsMessage::Text(serde_json::json!({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "subscribePoolCreations",
            "params": []
        }).to_string())).await?;
        info!("Connected to Helius WebSocket");

        let mut interval = interval(PRICE_CHECK_INTERVAL);
        loop {
            tokio::select! {
                Some(msg) = read.next() => {
                    match msg {
                        Ok(WsMessage::Text(text)) => {
                            if let Err(e) = self.handle_event(&text).await {
                                error!("Error handling event: {}", e);
                            }
                        }
                        Ok(WsMessage::Close(_)) => {
                            info!("WebSocket connection closed");
                            return Ok(());
                        }
                        Ok(_) => {
                            // Ignore other message types
                        }
                        Err(e) => {
                            error!("WebSocket error: {}", e);
                            return Err(e.into());
                        }
                    }
                }
                _ = interval.tick() => {
                    if let Err(e) = self.check_active_trades().await {
                        error!("Error checking active trades: {}", e);
                    }
                }
            }
        }
    }

    async fn handle_event(&self, text: &str) -> Result<()> {
        let event: serde_json::Value = serde_json::from_str(text)?;
        if let Some(params) = event.get("params") {
            let token_address = params
                .get("tokenAddress")
                .and_then(|v| v.as_str())
                .ok_or_else(|| anyhow!("Token address not found in event"))?;

            let creation_time = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("Time went backwards")
                .as_secs();

            info!("Token {} created at {}", token_address, creation_time);

            if self.is_valid_pool(token_address).await? {
                self.attempt_buy(token_address).await?;
            } else {
                info!("Token {} failed validation checks. Skipping.", token_address);
            }
        }

        Ok(())
    }

    async fn is_valid_pool(&self, token_address: &str) -> Result<bool> {
        let _guard = self.metadata_limiter.lock().await;
    
        let request = TokenMetadataRequest {
            mint_accounts: vec![token_address.to_string()],
            include_off_chain: true,
            include_on_chain: true,
        };
    
        let token_data = self.helius_client.get_token_metadata(&request).await?;
    
        if let Some(token_info) = token_data.first() {
            let is_raydium_pool = token_info.on_chain_metadata.as_ref()
                .and_then(|metadata| metadata.update_authority.as_ref())
                .map_or(false, |authority| authority == RAYDIUM_AMM_PROGRAM_ID);
    
            if !is_raydium_pool {
                info!("Token {} is not a Raydium pool. Skipping.", token_address);
                return Ok(false);
            }
    
            let mint_authority_revoked = token_info.on_chain_metadata.as_ref()
                .and_then(|metadata| metadata.mint_authority.as_ref())
                .map_or(true, |authority| authority.is_empty());
            let burnt = token_info.burnt.unwrap_or(false);
            let is_spl_token = token_info.token_standard.as_deref() == Some("SPL");
    
            let liquidity = token_info.market_data.as_ref()
                .and_then(|market_data| market_data.liquidity)
                .unwrap_or(0.0);
    
            let is_valid = mint_authority_revoked
                && !burnt
                && is_spl_token
                && liquidity >= self.config.get_float("min_liquidity")?
                && liquidity <= self.config.get_float("max_liquidity")?;
    
            info!(
                "Token {} validation: Mint Authority revoked: {}, Not burnt: {}, Raydium pool: true, SPL token: {}, Liquidity: {} SOL",
                token_address,
                mint_authority_revoked,
                !burnt,
                is_spl_token,
                liquidity
            );
    
            Ok(is_valid)
        } else {
            Err(anyhow!("Failed to get asset information for token {}", token_address))
        }
    }
    
    async fn get_token_price(&self, token_address: &str) -> Result<f64> {
        let cache = self.price_cache.lock().await;
        if let Some(price) = cache.get(token_address) {
            return Ok(price);
        }
        drop(cache);
    
        let _guard = self.metadata_limiter.lock().await;
    
        let request = TokenMetadataRequest {
            mint_accounts: vec![token_address.to_string()],
            include_off_chain: true,
            include_on_chain: true,
        };
    
        let token_data = self.helius_client.get_token_metadata(&request).await?;
    
        if let Some(token_info) = token_data.first() {
            if let Some(price) = token_info.price_info.as_ref().and_then(|pi| pi.price) {
                let mut cache = self.price_cache.lock().await;
                cache.set(token_address.to_string(), price);
                Ok(price)
            } else {
                Err(anyhow!("No price found for token {}", token_address))
            }
        } else {
            Err(anyhow!("No data found for token {}", token_address))
        }
    }

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

        let snipe_amount = self.config.get_float("snipe_amount")?;
        let snipe_amount_sol = snipe_amount * 1e9 as f64;

        for attempt in 1..=MAX_BUY_ATTEMPTS {
            info!("Buy attempt {} for token {}", attempt, token_address);

            match self.execute_buy(token_address, snipe_amount_sol as u64).await {
                Ok(_) => return Ok(()),
                Err(e) => {
                    warn!("Buy attempt {} failed: {}", attempt, e);
                    if attempt < MAX_BUY_ATTEMPTS {
                        sleep(BUY_ATTEMPT_DELAY).await;
                    }
                }
            }
        }

        error!("All buy attempts failed for token: {}", token_address);
        Ok(()) // We're not returning an error here as we want the bot to continue running
    }

    async fn execute_buy(&self, token_address: &str, amount: u64) -> Result<()> {
        let _guard = self.swap_limiter.lock().await;

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

        let quote_response = reqwest::Client::new()
            .post(jupiter_url)
            .query(&params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

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

        let swap_response = reqwest::Client::new()
            .post(swap_url)
            .json(&swap_params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        let transaction_data = base64::decode(swap_response["swapTransaction"].as_str().unwrap())?;
        let message = deserialize::<Message>(&transaction_data)?;
        let mut transaction = Transaction::new_unsigned(message);

        let recent_blockhash = self.get_recent_blockhash().await?;
        transaction.sign(&[&self.keypair], recent_blockhash);

        let signature = self.rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
        info!("Buy executed successfully. Signature: {}", signature);

        let current_price = self.get_token_price(token_address).await?;
        let take_profit_price = current_price * (1.0 + self.config.get_float("profit_target_percent")? / 100.0);
        let stop_loss_price = current_price * (1.0 - self.config.get_float("stop_loss_percent")? / 100.0);

        let trade = Trade {
            token_address: token_address.to_string(),
            amount,
            buy_price: current_price,
            take_profit_price,
            stop_loss_price,
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("Time went backwards")
                .as_secs(),
        };

        self.active_trades.lock().await.insert(token_address.to_string(), trade);

        info!(
            "Added trade to active trades. Buy price: {}, Take profit: {}, Stop loss: {}",
            current_price, take_profit_price, stop_loss_price
        );

        Ok(())
    }

    async fn check_active_trades(&self) -> Result<()> {
        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?;

                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());
                    }
                }
            }
        }

        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<()> {
        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 = serde_json::json!({
            "inputMint": token_address,
            "outputMint": WSOL_MINT,
            "amount": amount.to_string(),
            "slippageBps": (self.config.get_float("max_slippage")? * 100.0) as u64,
        });

        let quote_response = reqwest::Client::new()
            .post(jupiter_url)
            .query(&params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

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

        let swap_response = reqwest::Client::new()
            .post(swap_url)
            .json(&swap_params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        let transaction_data = base64::decode(swap_response["swapTransaction"].as_str().unwrap())?;
        let message = deserialize::<Message>(&transaction_data)?;
        let mut transaction = Transaction::new_unsigned(message);

        let recent_blockhash = self.get_recent_blockhash().await?;
        transaction.sign(&[&self.keypair], recent_blockhash);

        let signature = self.rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
        info!("Sell executed successfully. Signature: {}", signature);

        Ok(())
    }

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

    async fn save_state(&self) -> Result<()> {
        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())?;
        Ok(())
    }

    async fn load_state(&mut self) -> Result<()> {
        let mut file = match File::open(STATE_FILE) {
            Ok(file) => file,
            Err(_) => return Ok(()), // File doesn't exist, start with empty state
        };
        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;
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
    
    let bot = Bot::new("config.toml").await?;
    
    // Set up Ctrl+C handler
    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 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);
            }
        }
    }

    Ok(())
}   without changing the functions or removing code or weakening code please just fix it to not have these issues and still work as intended error[E0432]: unresolved import `helius_sdk::Client`                                         
  --> src/main.rs:25:34
   |
25 | use helius_sdk::{self as helius, Client as HeliusClient, TokenMetadataRequest};
   |                                  ^^^^^^^^^^^^^^^^^^^^^^ no `Client` in the root
   |
   = help: consider importing one of these items instead:
           reqwest::Client
           reqwest::blocking::Client
           solana_client::nonce_utils::Error::Client
           solana_sdk::client::Client
           tokio::net::windows::named_pipe::PipeEnd::Client

warning: unused import: `std::str::FromStr`
 --> src/main.rs:4:5
  |
4 | use std::str::FromStr;
  |     ^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `solana_sdk::pubkey::Pubkey`
  --> src/main.rs:13:5
   |
13 | use solana_sdk::pubkey::Pubkey;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: unused import: `self as helius`
  --> src/main.rs:25:18
   |
25 | use helius_sdk::{self as helius, Client as HeliusClient, TokenMetadataRequest};
   |                  ^^^^^^^^^^^^^^

error[E0061]: this function takes 2 arguments but 1 argument was supplied
   --> src/main.rs:117:29
    |
117 |         let helius_client = HeliusClient::new(&helius_api_key);
    |                             ^^^^^^^^^^^^^^^^^-----------------
    |                                              ||
    |                                              |expected `String`, found `&String`       
    |                                              argument #2 of type `helius_sdk::Cluster` is missing
    |
note: associated function defined here
   --> C:\Users\Orsen\.cargo\registry\src\index.crates.io-6f17d22bba15001f\helius-sdk-0.2.0\src\api\mod.rs:33:12
    |
33  |     pub fn new(api_key: String, cluster: Cluster) -> Helius {
    |            ^^^
help: consider removing the borrow
    |
117 -         let helius_client = HeliusClient::new(&helius_api_key);
117 +         let helius_client = HeliusClient::new(helius_api_key);
    |
help: provide the argument
    |
117 |         let helius_client = HeliusClient::new(/* std::string::String */, /* helius_sdk::Cluster */);
    |                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

error[E0560]: struct `TokenMetadataRequest` has no field named `include_on_chain`
   --> src/main.rs:226:13
    |
226 |             include_on_chain: true,
    |             ^^^^^^^^^^^^^^^^ `TokenMetadataRequest` does not have this field
    |
    = note: available fields are: `disable_cache`

warning: use of deprecated method `helius_sdk::api::token_metadata::<impl helius_sdk::Helius>::get_token_metadata`
   --> src/main.rs:229:45
    |
229 |         let token_data = self.helius_client.get_token_metadata(&request).await?;       
    |                                             ^^^^^^^^^^^^^^^^^^
    |
    = note: `#[warn(deprecated)]` on by default

error[E0277]: `Result<Vec<TokenMetadataResult>, reqwest::Error>` is not a future
   --> src/main.rs:229:74
    |
229 |         let token_data = self.helius_client.get_token_metadata(&request).await?;       
    |                                                                         -^^^^^
    |                                                                         ||
    |                                                                         |`Result<Vec<TokenMetadataResult>, reqwest::Error>` is not a future
    |                                                                         help: remove the `.await`
    |
    = help: the trait `futures_util::Future` is not implemented for `Result<Vec<TokenMetadataResult>, reqwest::Error>`, which is required by `Result<Vec<TokenMetadataResult>, reqwest::Error>: std::future::IntoFuture`
    = note: Result<Vec<TokenMetadataResult>, reqwest::Error> must be a future or must implement `IntoFuture` to be awaited
    = note: required for `Result<Vec<TokenMetadataResult>, reqwest::Error>` to implement `std::future::IntoFuture`

error[E0560]: struct `TokenMetadataRequest` has no field named `include_on_chain`
   --> src/main.rs:284:13
    |
284 |             include_on_chain: true,
    |             ^^^^^^^^^^^^^^^^ `TokenMetadataRequest` does not have this field
    |
    = note: available fields are: `disable_cache`

warning: use of deprecated method `helius_sdk::api::token_metadata::<impl helius_sdk::Helius>::get_token_metadata`
   --> src/main.rs:287:45
    |
287 |         let token_data = self.helius_client.get_token_metadata(&request).await?;       
    |                                             ^^^^^^^^^^^^^^^^^^

error[E0277]: `Result<Vec<TokenMetadataResult>, reqwest::Error>` is not a future
   --> src/main.rs:287:74
    |
287 |         let token_data = self.helius_client.get_token_metadata(&request).await?;       
    |                                                                         -^^^^^
    |                                                                         ||
    |                                                                         |`Result<Vec<TokenMetadataResult>, reqwest::Error>` is not a future
    |                                                                         help: remove the `.await`
    |
    = help: the trait `futures_util::Future` is not implemented for `Result<Vec<TokenMetadataResult>, reqwest::Error>`, which is required by `Result<Vec<TokenMetadataResult>, reqwest::Error>: std::future::IntoFuture`
    = note: Result<Vec<TokenMetadataResult>, reqwest::Error> must be a future or must implement `IntoFuture` to be awaited
    = note: required for `Result<Vec<TokenMetadataResult>, reqwest::Error>` to implement `std::future::IntoFuture`

Some errors have detailed explanations: E0061, E0277, E0432, E0560.
For more information about an error, try `rustc --explain E0061`.
warning: `solana-sniper-bot` (bin "solana-sniper-bot" test) generated 5 warnings
error: could not compile `solana-sniper-bot` (bin "solana-sniper-bot" test) due to 6 previous errors; 5 warnings emitted
warning: build failed, waiting for other jobs to finish...
warning: `solana-sniper-bot` (bin "solana-sniper-bot") generated 5 warnings (5 duplicates)  
error: could not compile `solana-sniper-bot` (bin "solana-sniper-bot") due to 6 previous errors; 5 warnings emitted