use std::time::Instant;

use axum::{Json, extract::{Path, State}};
use barter_instrument::exchange::ExchangeId;
use serde::Serialize;

use crate::error::ApiError;
use crate::exchange_client::{
    self, CachedInstruments, InstrumentPair,
};
use crate::state::AppState;

#[derive(Debug, Serialize)]
pub struct ExchangeResponse {
    pub name: String,
    pub supported_sub_kinds: Vec<&'static str>,
    pub supports_instrument_lookup: bool,
}

const ALL_EXCHANGES: &[ExchangeId] = &[
    ExchangeId::BinanceFuturesCoin,
    ExchangeId::BinanceFuturesUsd,
    ExchangeId::BinanceOptions,
    ExchangeId::BinancePortfolioMargin,
    ExchangeId::BinanceSpot,
    ExchangeId::BinanceUs,
    ExchangeId::Bitazza,
    ExchangeId::Bitfinex,
    ExchangeId::Bitflyer,
    ExchangeId::Bitget,
    ExchangeId::Bitmart,
    ExchangeId::BitmartFuturesUsd,
    ExchangeId::Bitmex,
    ExchangeId::Bitso,
    ExchangeId::Bitstamp,
    ExchangeId::Bitvavo,
    ExchangeId::Bithumb,
    ExchangeId::BybitPerpetualsUsd,
    ExchangeId::BybitSpot,
    ExchangeId::Cexio,
    ExchangeId::Coinbase,
    ExchangeId::CoinbaseInternational,
    ExchangeId::Cryptocom,
    ExchangeId::Deribit,
    ExchangeId::GateioFuturesBtc,
    ExchangeId::GateioFuturesUsd,
    ExchangeId::GateioOptions,
    ExchangeId::GateioPerpetualsBtc,
    ExchangeId::GateioPerpetualsUsd,
    ExchangeId::GateioSpot,
    ExchangeId::Gemini,
    ExchangeId::Hitbtc,
    ExchangeId::Htx,
    ExchangeId::Kraken,
    ExchangeId::Kucoin,
    ExchangeId::Liquid,
    ExchangeId::Mexc,
    ExchangeId::Okx,
    ExchangeId::Poloniex,
];

pub fn supported_sub_kinds(exchange: ExchangeId) -> &'static [&'static str] {
    match exchange {
        ExchangeId::BinanceSpot | ExchangeId::BinanceUs => {
            &["PublicTrades", "OrderBooksL1", "OrderBooksL2"]
        }
        ExchangeId::BinanceFuturesUsd => {
            &["PublicTrades", "OrderBooksL1", "OrderBooksL2", "Liquidations"]
        }
        ExchangeId::BinanceFuturesCoin => {
            &["PublicTrades", "OrderBooksL1", "OrderBooksL2"]
        }
        ExchangeId::BybitSpot | ExchangeId::BybitPerpetualsUsd => {
            &["PublicTrades", "OrderBooksL1", "OrderBooksL2"]
        }
        ExchangeId::Kraken => &["PublicTrades", "OrderBooksL1"],
        _ => &["PublicTrades"],
    }
}

fn parse_exchange_id(name: &str) -> Result<ExchangeId, ApiError> {
    serde_json::from_value(serde_json::Value::String(name.to_owned()))
        .map_err(|_| ApiError::BadRequest(format!("invalid exchange: {name}")))
}

/// GET /api/v1/exchanges
pub async fn list_exchanges() -> Json<Vec<ExchangeResponse>> {
    let responses: Vec<ExchangeResponse> = ALL_EXCHANGES
        .iter()
        .map(|ex| ExchangeResponse {
            name: ex.as_str().to_owned(),
            supported_sub_kinds: supported_sub_kinds(*ex).to_vec(),
            supports_instrument_lookup: exchange_client::supports_instrument_lookup(*ex),
        })
        .collect();
    Json(responses)
}

/// GET /api/v1/exchanges/{name}/sub-kinds
pub async fn get_exchange_sub_kinds(
    Path(name): Path<String>,
) -> Result<Json<Vec<&'static str>>, ApiError> {
    let exchange_id = parse_exchange_id(&name)?;
    Ok(Json(supported_sub_kinds(exchange_id).to_vec()))
}

/// GET /api/v1/exchanges/{name}/instruments
pub async fn get_exchange_instruments(
    State(state): State<AppState>,
    Path(name): Path<String>,
) -> Result<Json<Vec<InstrumentPair>>, ApiError> {
    let exchange_id = parse_exchange_id(&name)?;

    if !exchange_client::supports_instrument_lookup(exchange_id) {
        return Err(ApiError::BadRequest(format!(
            "exchange {name} does not support instrument lookup"
        )));
    }

    // Check cache (read lock)
    {
        let cache = state.instrument_cache.read().await;
        if let Some(cached) = cache.get(&exchange_id) {
            if cached.is_fresh() {
                return Ok(Json(cached.pairs.clone()));
            }
        }
    }

    // Fetch fresh data
    let pairs = exchange_client::fetch_instruments(&state.http_client, exchange_id).await?;

    // Update cache (write lock)
    {
        let mut cache = state.instrument_cache.write().await;
        cache.insert(
            exchange_id,
            CachedInstruments {
                pairs: pairs.clone(),
                fetched_at: Instant::now(),
            },
        );
    }

    Ok(Json(pairs))
}
