use axum::{
    Json,
    extract::{Path, Query, State},
};
use barter_instrument::exchange::ExchangeId;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::error::ApiError;
use crate::state::AppState;
use swym_dal::models::market_subscription::{EnrichedSubscriptionRow, MarketSubscriptionRow};
use swym_dal::repo::{instrument, market_subscription};

// -- Request / Response types --

#[derive(Debug, Deserialize)]
pub struct CreateSubscriptionRequest {
    pub exchange: String,
    pub pair: String,
    pub sub_kinds: Vec<String>,
}

#[derive(Debug, Deserialize)]
pub struct ListQuery {
    #[serde(default = "default_active_only")]
    pub active_only: bool,
}

fn default_active_only() -> bool {
    true
}

#[derive(Debug, Serialize)]
pub struct SubscriptionResponse {
    pub id: i32,
    pub instrument_id: i32,
    pub sub_kind: String,
    pub active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl From<MarketSubscriptionRow> for SubscriptionResponse {
    fn from(row: MarketSubscriptionRow) -> Self {
        Self {
            id: row.id,
            instrument_id: row.instrument_id,
            sub_kind: row.sub_kind,
            active: row.active,
            created_at: row.created_at,
            updated_at: row.updated_at,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct EnrichedSubscriptionResponse {
    pub id: i32,
    pub instrument_id: i32,
    pub sub_kind: String,
    pub active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub exchange: String,
    pub base_asset: String,
    pub quote_asset: String,
}

impl From<EnrichedSubscriptionRow> for EnrichedSubscriptionResponse {
    fn from(row: EnrichedSubscriptionRow) -> Self {
        Self {
            id: row.id,
            instrument_id: row.instrument_id,
            sub_kind: row.sub_kind,
            active: row.active,
            created_at: row.created_at,
            updated_at: row.updated_at,
            exchange: row.exchange_name,
            base_asset: row.base_asset,
            quote_asset: row.quote_asset,
        }
    }
}

const VALID_SUB_KINDS: &[&str] = &[
    "PublicTrades",
    "OrderBooksL1",
    "OrderBooksL2",
    "OrderBooksL3",
    "Liquidations",
    "Candles",
];

/// POST /api/v1/market-subscriptions
pub async fn create_subscriptions(
    State(state): State<AppState>,
    Json(req): Json<CreateSubscriptionRequest>,
) -> Result<(axum::http::StatusCode, Json<Vec<SubscriptionResponse>>), ApiError> {
    // Validate exchange name parses as ExchangeId
    let exchange_id: ExchangeId = serde_json::from_value(serde_json::Value::String(
        req.exchange.clone(),
    ))
    .map_err(|_| ApiError::BadRequest(format!("invalid exchange: {}", req.exchange)))?;

    // Validate sub_kinds
    for sk in &req.sub_kinds {
        if !VALID_SUB_KINDS.contains(&sk.as_str()) {
            return Err(ApiError::BadRequest(format!("invalid sub_kind: {sk}")));
        }
    }
    if req.sub_kinds.is_empty() {
        return Err(ApiError::BadRequest(
            "sub_kinds must not be empty".to_owned(),
        ));
    }

    // Validate sub_kinds are supported by this exchange
    let supported = super::exchanges::supported_sub_kinds(exchange_id);
    for sk in &req.sub_kinds {
        if !supported.contains(&sk.as_str()) {
            return Err(ApiError::BadRequest(format!(
                "exchange {} does not support {sk}",
                req.exchange
            )));
        }
    }

    // Split pair on '-' to get base/quote
    let parts: Vec<&str> = req.pair.split('-').collect();
    if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
        return Err(ApiError::BadRequest(format!(
            "invalid pair format '{}', expected 'BASE-QUOTE' (e.g. 'BTC-USDT')",
            req.pair
        )));
    }
    let base = parts[0];
    let quote = parts[1];

    // Derive name_exchange = uppercase(base + quote) e.g. "BTCUSDT"
    let name_exchange = format!("{}{}", base.to_uppercase(), quote.to_uppercase());

    // Upsert exchange using the snake_case serde name
    let db_exchange_id =
        instrument::ensure_exchange(&state.pool, exchange_id.as_str()).await?;

    // Upsert instrument
    let db_instrument_id = instrument::ensure_instrument(
        &state.pool,
        db_exchange_id,
        &name_exchange,
        base,
        quote,
        "spot",
    )
    .await?;

    // Upsert one market_subscription row per sub_kind
    let mut created = Vec::with_capacity(req.sub_kinds.len());
    for sub_kind in &req.sub_kinds {
        let row =
            market_subscription::upsert(&state.pool, db_instrument_id, sub_kind).await?;
        created.push(SubscriptionResponse::from(row));
    }

    Ok((axum::http::StatusCode::CREATED, Json(created)))
}

/// GET /api/v1/market-subscriptions
pub async fn list_subscriptions(
    State(state): State<AppState>,
    Query(query): Query<ListQuery>,
) -> Result<Json<Vec<EnrichedSubscriptionResponse>>, ApiError> {
    let rows = market_subscription::list_enriched(&state.pool, query.active_only).await?;
    let responses: Vec<EnrichedSubscriptionResponse> = rows.into_iter().map(Into::into).collect();
    Ok(Json(responses))
}

/// DELETE /api/v1/market-subscriptions/{id}
pub async fn deactivate_subscription(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> Result<Json<SubscriptionResponse>, ApiError> {
    let row = market_subscription::deactivate(&state.pool, id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("subscription {id} not found")))?;

    Ok(Json(row.into()))
}
