//! Synor DEX SDK Types

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;

/// DEX SDK Error
#[derive(Error, Debug)]
pub enum DexError {
    #[error("HTTP error: {message}")]
    Http { message: String, code: Option<String>, status: Option<u16> },

    #[error("Client closed")]
    ClientClosed,

    #[error("WebSocket error: {0}")]
    WebSocket(String),

    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),

    #[error("Request error: {0}")]
    Request(#[from] reqwest::Error),
}

/// Pool type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PoolType {
    ConstantProduct, // x * y = k
    Stable,          // Optimized for stablecoins
    Concentrated,    // Uniswap v3 style
}

/// Position side for perpetuals
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PositionSide {
    Long,
    Short,
}

/// Order type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OrderType {
    Market,
    Limit,
    StopMarket,
    StopLimit,
    TakeProfit,
    TakeProfitLimit,
}

/// Margin type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum MarginType {
    #[default]
    Cross,
    Isolated,
}

/// Time in force
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum TimeInForce {
    #[default]
    GTC, // Good Till Cancel
    IOC, // Immediate Or Cancel
    FOK, // Fill Or Kill
    GTD, // Good Till Date
}

/// Order status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OrderStatus {
    Pending,
    Open,
    PartiallyFilled,
    Filled,
    Cancelled,
    Expired,
}

/// DEX client configuration
#[derive(Debug, Clone)]
pub struct DexConfig {
    pub api_key: String,
    pub endpoint: String,
    pub ws_endpoint: String,
    pub timeout_ms: u64,
    pub retries: u32,
    pub debug: bool,
}

impl DexConfig {
    pub fn new(api_key: impl Into<String>) -> Self {
        Self {
            api_key: api_key.into(),
            endpoint: "https://dex.synor.io/v1".to_string(),
            ws_endpoint: "wss://dex.synor.io/v1/ws".to_string(),
            timeout_ms: 30000,
            retries: 3,
            debug: false,
        }
    }

    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.endpoint = endpoint.into();
        self
    }

    pub fn with_ws_endpoint(mut self, ws_endpoint: impl Into<String>) -> Self {
        self.ws_endpoint = ws_endpoint.into();
        self
    }

    pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
        self.timeout_ms = timeout_ms;
        self
    }

    pub fn with_retries(mut self, retries: u32) -> Self {
        self.retries = retries;
        self
    }

    pub fn with_debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }
}

/// Token information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
    pub address: String,
    pub symbol: String,
    pub name: String,
    pub decimals: u8,
    pub total_supply: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub price_usd: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub logo_url: Option<String>,
    #[serde(default)]
    pub verified: bool,
}

/// Liquidity pool
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pool {
    pub id: String,
    pub token_a: Token,
    pub token_b: Token,
    pub pool_type: PoolType,
    pub reserve_a: String,
    pub reserve_b: String,
    pub fee: f64,
    pub tvl_usd: f64,
    pub volume_24h: f64,
    pub apr: f64,
    pub lp_token_address: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tick_spacing: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sqrt_price: Option<String>,
}

/// Pool filter
#[derive(Debug, Clone, Default)]
pub struct PoolFilter {
    pub tokens: Option<Vec<String>>,
    pub min_tvl: Option<f64>,
    pub min_volume_24h: Option<f64>,
    pub verified: Option<bool>,
    pub limit: Option<u32>,
    pub offset: Option<u32>,
}

/// Swap quote
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quote {
    pub token_in: String,
    pub token_out: String,
    pub amount_in: String,
    pub amount_out: String,
    pub price_impact: f64,
    pub route: Vec<String>,
    pub fee: String,
    pub minimum_received: String,
    pub expires_at: i64,
}

/// Quote parameters
#[derive(Debug, Clone)]
pub struct QuoteParams {
    pub token_in: String,
    pub token_out: String,
    pub amount_in: String,
    pub slippage: Option<f64>,
}

/// Swap parameters
#[derive(Debug, Clone)]
pub struct SwapParams {
    pub token_in: String,
    pub token_out: String,
    pub amount_in: String,
    pub min_amount_out: String,
    pub deadline: Option<i64>,
    pub recipient: Option<String>,
}

/// Swap result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwapResult {
    pub transaction_hash: String,
    pub amount_in: String,
    pub amount_out: String,
    pub effective_price: f64,
    pub fee_paid: String,
    pub route: Vec<String>,
}

/// Add liquidity parameters
#[derive(Debug, Clone)]
pub struct AddLiquidityParams {
    pub token_a: String,
    pub token_b: String,
    pub amount_a: String,
    pub amount_b: String,
    pub min_amount_a: Option<String>,
    pub min_amount_b: Option<String>,
    pub deadline: Option<i64>,
    pub tick_lower: Option<i32>,
    pub tick_upper: Option<i32>,
}

/// Remove liquidity parameters
#[derive(Debug, Clone)]
pub struct RemoveLiquidityParams {
    pub pool: String,
    pub lp_amount: String,
    pub min_amount_a: Option<String>,
    pub min_amount_b: Option<String>,
    pub deadline: Option<i64>,
}

/// Liquidity result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityResult {
    pub transaction_hash: String,
    pub amount_a: String,
    pub amount_b: String,
    pub lp_tokens: String,
    pub pool_share: f64,
}

/// LP position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LPPosition {
    pub pool_id: String,
    pub lp_tokens: String,
    pub token_a_amount: String,
    pub token_b_amount: String,
    pub value_usd: f64,
    pub unclaimed_fees_a: String,
    pub unclaimed_fees_b: String,
    pub impermanent_loss: f64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tick_lower: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tick_upper: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub in_range: Option<bool>,
}

/// Perpetual market
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerpMarket {
    pub symbol: String,
    pub base_asset: String,
    pub quote_asset: String,
    pub index_price: f64,
    pub mark_price: f64,
    pub funding_rate: f64,
    pub next_funding_time: i64,
    pub open_interest: String,
    pub volume_24h: f64,
    pub price_change_24h: f64,
    pub max_leverage: u32,
    pub min_order_size: String,
    pub tick_size: f64,
    pub maintenance_margin: f64,
    pub initial_margin: f64,
}

/// Open position parameters
#[derive(Debug, Clone)]
pub struct OpenPositionParams {
    pub market: String,
    pub side: PositionSide,
    pub size: String,
    pub leverage: u32, // 1-100x
    pub order_type: OrderType,
    pub limit_price: Option<f64>,
    pub stop_loss: Option<f64>,
    pub take_profit: Option<f64>,
    pub margin_type: MarginType,
    pub reduce_only: bool,
}

/// Close position parameters
#[derive(Debug, Clone)]
pub struct ClosePositionParams {
    pub market: String,
    pub size: Option<String>, // None = close entire position
    pub order_type: OrderType,
    pub limit_price: Option<f64>,
}

/// Modify position parameters
#[derive(Debug, Clone)]
pub struct ModifyPositionParams {
    pub position_id: String,
    pub new_leverage: Option<u32>,
    pub new_margin: Option<String>,
    pub new_stop_loss: Option<f64>,
    pub new_take_profit: Option<f64>,
}

/// Perpetual position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerpPosition {
    pub id: String,
    pub market: String,
    pub side: PositionSide,
    pub size: String,
    pub entry_price: f64,
    pub mark_price: f64,
    pub liquidation_price: f64,
    pub margin: String,
    pub leverage: u32,
    pub unrealized_pnl: String,
    pub realized_pnl: String,
    pub margin_ratio: f64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stop_loss: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub take_profit: Option<f64>,
    #[serde(default)]
    pub created_at: i64,
    #[serde(default)]
    pub updated_at: i64,
}

/// Perpetual order
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerpOrder {
    pub id: String,
    pub market: String,
    pub side: PositionSide,
    pub order_type: OrderType,
    pub size: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub price: Option<f64>,
    #[serde(default)]
    pub filled_size: String,
    pub status: OrderStatus,
    #[serde(default)]
    pub reduce_only: bool,
    #[serde(default)]
    pub created_at: i64,
}

/// Funding payment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundingPayment {
    pub market: String,
    pub amount: String,
    pub rate: f64,
    pub position_size: String,
    pub timestamp: i64,
}

/// Order book entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookEntry {
    pub price: f64,
    pub size: String,
    pub orders: u32,
}

/// Order book
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBook {
    pub market: String,
    pub bids: Vec<OrderBookEntry>,
    pub asks: Vec<OrderBookEntry>,
    pub timestamp: i64,
}

/// Limit order parameters
#[derive(Debug, Clone)]
pub struct LimitOrderParams {
    pub market: String,
    pub side: String, // "buy" or "sell"
    pub price: f64,
    pub size: String,
    pub time_in_force: TimeInForce,
    pub post_only: bool,
}

/// Limit order
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
    pub id: String,
    pub market: String,
    pub side: String,
    pub price: f64,
    pub size: String,
    pub filled_size: String,
    pub status: OrderStatus,
    pub time_in_force: TimeInForce,
    pub post_only: bool,
    pub created_at: i64,
    pub updated_at: i64,
}

/// Yield farm
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Farm {
    pub id: String,
    pub name: String,
    pub stake_token: Token,
    pub reward_tokens: Vec<Token>,
    pub tvl_usd: f64,
    pub apr: f64,
    pub daily_rewards: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lockup_period: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_stake: Option<String>,
}

/// Stake parameters
#[derive(Debug, Clone)]
pub struct StakeParams {
    pub farm: String,
    pub amount: String,
}

/// Farm position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmPosition {
    pub farm_id: String,
    pub staked_amount: String,
    pub pending_rewards: Vec<String>,
    pub staked_at: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unlock_at: Option<i64>,
}

/// OHLCV candle data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OHLCV {
    pub timestamp: i64,
    pub open: f64,
    pub high: f64,
    pub low: f64,
    pub close: f64,
    pub volume: f64,
}

/// Trade history entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeHistory {
    pub id: String,
    pub market: String,
    pub side: String,
    pub price: f64,
    pub size: String,
    pub timestamp: i64,
    pub maker: String,
    pub taker: String,
}

/// Volume statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolumeStats {
    pub volume_24h: f64,
    pub volume_7d: f64,
    pub volume_30d: f64,
    pub trades_24h: u64,
    pub unique_traders_24h: u64,
}

/// TVL statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TVLStats {
    pub total_tvl: f64,
    pub pools_tvl: f64,
    pub farms_tvl: f64,
    pub perps_tvl: f64,
}

/// Subscription handle
pub struct Subscription {
    pub id: String,
    pub channel: String,
    cancel_tx: Option<tokio::sync::oneshot::Sender<()>>,
}

impl Subscription {
    pub fn new(id: String, channel: String, cancel_tx: tokio::sync::oneshot::Sender<()>) -> Self {
        Self {
            id,
            channel,
            cancel_tx: Some(cancel_tx),
        }
    }

    pub fn cancel(mut self) {
        if let Some(tx) = self.cancel_tx.take() {
            let _ = tx.send(());
        }
    }
}

/// Claim rewards result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimRewardsResult {
    pub amount: String,
    pub transaction_hash: String,
}

/// Funding rate info
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundingRateInfo {
    pub rate: f64,
    pub next_time: i64,
}
