//! Synor DEX Client

use crate::dex::types::*;
use reqwest::Client;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::json;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// Main DEX client
pub struct SynorDex {
    config: DexConfig,
    client: Client,
    closed: Arc<AtomicBool>,
    pub perps: PerpsClient,
    pub orderbook: OrderBookClient,
    pub farms: FarmsClient,
}

impl SynorDex {
    /// Create a new SynorDex client
    pub fn new(config: DexConfig) -> Result<Self, DexError> {
        let client = Client::builder()
            .timeout(Duration::from_millis(config.timeout_ms))
            .build()?;

        let closed = Arc::new(AtomicBool::new(false));

        Ok(Self {
            perps: PerpsClient::new(config.clone(), client.clone(), closed.clone()),
            orderbook: OrderBookClient::new(config.clone(), client.clone(), closed.clone()),
            farms: FarmsClient::new(config.clone(), client.clone(), closed.clone()),
            config,
            client,
            closed,
        })
    }

    // Token Operations

    /// Get token information
    pub async fn get_token(&self, address: &str) -> Result<Token, DexError> {
        self.get(&format!("/tokens/{}", address)).await
    }

    /// List all tokens
    pub async fn list_tokens(&self) -> Result<Vec<Token>, DexError> {
        self.get("/tokens").await
    }

    /// Search tokens
    pub async fn search_tokens(&self, query: &str) -> Result<Vec<Token>, DexError> {
        self.get(&format!("/tokens/search?q={}", urlencoding::encode(query))).await
    }

    // Pool Operations

    /// Get pool by token pair
    pub async fn get_pool(&self, token_a: &str, token_b: &str) -> Result<Pool, DexError> {
        self.get(&format!("/pools/{}/{}", token_a, token_b)).await
    }

    /// Get pool by ID
    pub async fn get_pool_by_id(&self, pool_id: &str) -> Result<Pool, DexError> {
        self.get(&format!("/pools/{}", pool_id)).await
    }

    /// List pools with optional filtering
    pub async fn list_pools(&self, filter: Option<PoolFilter>) -> Result<Vec<Pool>, DexError> {
        let mut params = Vec::new();

        if let Some(f) = filter {
            if let Some(tokens) = f.tokens {
                params.push(format!("tokens={}", tokens.join(",")));
            }
            if let Some(min_tvl) = f.min_tvl {
                params.push(format!("min_tvl={}", min_tvl));
            }
            if let Some(min_volume) = f.min_volume_24h {
                params.push(format!("min_volume={}", min_volume));
            }
            if let Some(verified) = f.verified {
                params.push(format!("verified={}", verified));
            }
            if let Some(limit) = f.limit {
                params.push(format!("limit={}", limit));
            }
            if let Some(offset) = f.offset {
                params.push(format!("offset={}", offset));
            }
        }

        let path = if params.is_empty() {
            "/pools".to_string()
        } else {
            format!("/pools?{}", params.join("&"))
        };

        self.get(&path).await
    }

    // Swap Operations

    /// Get swap quote
    pub async fn get_quote(&self, params: QuoteParams) -> Result<Quote, DexError> {
        self.post("/swap/quote", json!({
            "token_in": params.token_in,
            "token_out": params.token_out,
            "amount_in": params.amount_in,
            "slippage": params.slippage.unwrap_or(0.005),
        })).await
    }

    /// Execute swap
    pub async fn swap(&self, params: SwapParams) -> Result<SwapResult, DexError> {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;
        let deadline = params.deadline.unwrap_or(now + 1200);

        let mut body = json!({
            "token_in": params.token_in,
            "token_out": params.token_out,
            "amount_in": params.amount_in,
            "min_amount_out": params.min_amount_out,
            "deadline": deadline,
        });

        if let Some(recipient) = params.recipient {
            body["recipient"] = json!(recipient);
        }

        self.post("/swap", body).await
    }

    // Liquidity Operations

    /// Add liquidity to a pool
    pub async fn add_liquidity(&self, params: AddLiquidityParams) -> Result<LiquidityResult, DexError> {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;
        let deadline = params.deadline.unwrap_or(now + 1200);

        let mut body = json!({
            "token_a": params.token_a,
            "token_b": params.token_b,
            "amount_a": params.amount_a,
            "amount_b": params.amount_b,
            "deadline": deadline,
        });

        if let Some(min_a) = params.min_amount_a {
            body["min_amount_a"] = json!(min_a);
        }
        if let Some(min_b) = params.min_amount_b {
            body["min_amount_b"] = json!(min_b);
        }

        self.post("/liquidity/add", body).await
    }

    /// Remove liquidity from a pool
    pub async fn remove_liquidity(&self, params: RemoveLiquidityParams) -> Result<LiquidityResult, DexError> {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;
        let deadline = params.deadline.unwrap_or(now + 1200);

        let mut body = json!({
            "pool": params.pool,
            "lp_amount": params.lp_amount,
            "deadline": deadline,
        });

        if let Some(min_a) = params.min_amount_a {
            body["min_amount_a"] = json!(min_a);
        }
        if let Some(min_b) = params.min_amount_b {
            body["min_amount_b"] = json!(min_b);
        }

        self.post("/liquidity/remove", body).await
    }

    /// Get all LP positions
    pub async fn get_my_positions(&self) -> Result<Vec<LPPosition>, DexError> {
        self.get("/liquidity/positions").await
    }

    // Analytics

    /// Get price history (OHLCV candles)
    pub async fn get_price_history(&self, pair: &str, interval: &str, limit: u32) -> Result<Vec<OHLCV>, DexError> {
        self.get(&format!("/analytics/candles/{}?interval={}&limit={}", pair, interval, limit)).await
    }

    /// Get trade history
    pub async fn get_trade_history(&self, pair: &str, limit: u32) -> Result<Vec<TradeHistory>, DexError> {
        self.get(&format!("/analytics/trades/{}?limit={}", pair, limit)).await
    }

    /// Get volume statistics
    pub async fn get_volume_stats(&self) -> Result<VolumeStats, DexError> {
        self.get("/analytics/volume").await
    }

    /// Get TVL statistics
    pub async fn get_tvl(&self) -> Result<TVLStats, DexError> {
        self.get("/analytics/tvl").await
    }

    // Lifecycle

    /// Health check
    pub async fn health_check(&self) -> bool {
        #[derive(Deserialize)]
        struct Health {
            status: String,
        }

        match self.get::<Health>("/health").await {
            Ok(h) => h.status == "healthy",
            Err(_) => false,
        }
    }

    /// Close the client
    pub fn close(&self) {
        self.closed.store(true, Ordering::SeqCst);
    }

    /// Check if client is closed
    pub fn is_closed(&self) -> bool {
        self.closed.load(Ordering::SeqCst)
    }

    // Internal methods

    pub(crate) async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
        self.request("GET", path, None::<()>).await
    }

    pub(crate) async fn post<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: B) -> Result<T, DexError> {
        self.request("POST", path, Some(body)).await
    }

    pub(crate) async fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T, DexError> {
        self.request("DELETE", path, None::<()>).await
    }

    async fn request<T: DeserializeOwned, B: Serialize>(
        &self,
        method: &str,
        path: &str,
        body: Option<B>,
    ) -> Result<T, DexError> {
        if self.closed.load(Ordering::SeqCst) {
            return Err(DexError::ClientClosed);
        }

        let mut last_error = None;

        for attempt in 0..self.config.retries {
            let url = format!("{}{}", self.config.endpoint, path);

            let mut request = match method {
                "GET" => self.client.get(&url),
                "POST" => self.client.post(&url),
                "DELETE" => self.client.delete(&url),
                _ => return Err(DexError::Http {
                    message: format!("Unknown method: {}", method),
                    code: None,
                    status: None,
                }),
            };

            request = request
                .header("Content-Type", "application/json")
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("X-SDK-Version", "rust/0.1.0");

            if let Some(ref b) = body {
                request = request.json(b);
            }

            match request.send().await {
                Ok(response) => {
                    let status = response.status();

                    if !status.is_success() {
                        #[derive(Deserialize, Default)]
                        struct ErrorResponse {
                            message: Option<String>,
                            code: Option<String>,
                        }

                        let error: ErrorResponse = response.json().await.unwrap_or_default();
                        return Err(DexError::Http {
                            message: error.message.unwrap_or_else(|| format!("HTTP {}", status)),
                            code: error.code,
                            status: Some(status.as_u16()),
                        });
                    }

                    return response.json().await.map_err(DexError::from);
                }
                Err(e) => {
                    last_error = Some(e);
                    if self.config.debug {
                        eprintln!("Attempt {} failed: {:?}", attempt + 1, last_error);
                    }
                    if attempt < self.config.retries - 1 {
                        tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
                    }
                }
            }
        }

        Err(last_error.map(DexError::from).unwrap_or(DexError::Http {
            message: "Unknown error".to_string(),
            code: None,
            status: None,
        }))
    }
}

use crate::dex::{PerpsClient, OrderBookClient, FarmsClient};
