use axum::{
    Json,
    extract::{Path, State},
    http::StatusCode,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::error::ApiError;
use crate::exchange_client;
use crate::state::AppState;
use swym_dal::repo::{instrument, market_event};

const VALID_INTERVALS: &[&str] = &["1m", "5m", "15m", "1h", "4h", "1d"];

/// POST /api/v1/market-candles/backfill
#[derive(Debug, Deserialize)]
pub struct BackfillRequest {
    pub exchange: String,
    pub symbol: String,
    pub interval: String,
    pub from: DateTime<Utc>,
    pub to: DateTime<Utc>,
}

#[derive(Debug, Serialize)]
pub struct BackfillResponse {
    pub inserted: u64,
    pub interval: String,
    pub from: DateTime<Utc>,
    pub to: DateTime<Utc>,
}

pub async fn backfill_candles(
    State(state): State<AppState>,
    Json(req): Json<BackfillRequest>,
) -> Result<(StatusCode, Json<BackfillResponse>), ApiError> {
    if !VALID_INTERVALS.contains(&req.interval.as_str()) {
        return Err(ApiError::BadRequest(format!(
            "invalid interval '{}'; valid values: {}",
            req.interval,
            VALID_INTERVALS.join(", ")
        )));
    }

    if req.to <= req.from {
        return Err(ApiError::BadRequest("'to' must be after 'from'".into()));
    }

    // Parse and resolve exchange/instrument.
    let exchange_id: barter_instrument::exchange::ExchangeId =
        serde_json::from_value(serde_json::Value::String(req.exchange.clone()))
            .map_err(|_| ApiError::BadRequest(format!("unknown exchange '{}'", req.exchange)))?;

    let instrument_row = instrument::find_by_exchange_and_name(
        &state.pool,
        &req.exchange,
        &req.symbol,
    )
    .await?
    .ok_or_else(|| {
        ApiError::BadRequest(format!(
            "instrument {} on {} not found; ensure a market subscription or ingest config exists",
            req.symbol, req.exchange
        ))
    })?;

    // Fetch candles from Binance.
    let klines = exchange_client::fetch_klines(
        &state.http_client,
        exchange_id,
        &req.symbol,
        &req.interval,
        req.from,
        req.to,
    )
    .await?;

    if klines.is_empty() {
        return Ok((
            StatusCode::OK,
            Json(BackfillResponse {
                inserted: 0,
                interval: req.interval,
                from: req.from,
                to: req.to,
            }),
        ));
    }

    // Convert to InsertCandle and batch-insert in chunks of 500.
    let inserts: Vec<market_event::InsertCandle> = klines
        .into_iter()
        .map(|k| market_event::InsertCandle {
            instrument_id: instrument_row.id,
            interval: req.interval.clone(),
            open_time: k.open_time,
            close_time: k.close_time,
            open: k.open,
            high: k.high,
            low: k.low,
            close: k.close,
            volume: k.volume,
            trade_count: k.trade_count,
        })
        .collect();

    let mut total_inserted = 0u64;
    for chunk in inserts.chunks(500) {
        total_inserted += market_event::batch_insert_candles(&state.pool, chunk).await?;
    }

    Ok((
        StatusCode::CREATED,
        Json(BackfillResponse {
            inserted: total_inserted,
            interval: req.interval,
            from: req.from,
            to: req.to,
        }),
    ))
}

/// GET /api/v1/market-candles/coverage/{exchange}/{symbol}
#[derive(Debug, Serialize)]
pub struct CoverageEntry {
    pub interval: String,
    pub first_open: DateTime<Utc>,
    pub last_close: DateTime<Utc>,
    /// Actual number of candles stored in the database for this interval.
    pub count: i64,
    /// Expected number of candles for the available range (derived from interval duration).
    pub expected_count: i64,
    /// Coverage as a percentage (0–100). Values below 95 indicate gaps in the data.
    pub coverage_pct: f64,
}

pub async fn get_candle_coverage(
    State(state): State<AppState>,
    Path((exchange, symbol)): Path<(String, String)>,
) -> Result<Json<Vec<CoverageEntry>>, ApiError> {
    let instrument_row = instrument::find_by_exchange_and_name(&state.pool, &exchange, &symbol)
        .await?
        .ok_or_else(|| {
            ApiError::NotFound(format!("instrument {symbol} on {exchange} not found"))
        })?;

    let rows = market_event::candle_coverage(&state.pool, instrument_row.id).await?;

    let entries = rows
        .into_iter()
        .map(|(interval, first_open, last_close, count)| {
            let range_secs = (last_close - first_open).num_seconds().max(0) as u64;
            let interval_secs =
                swym_dal::models::strategy_config::parse_interval_secs(&interval).unwrap_or(1);
            let expected_count = (range_secs / interval_secs) as i64;
            let coverage_pct = if expected_count > 0 {
                (count as f64 / expected_count as f64 * 100.0).min(100.0)
            } else {
                100.0
            };
            CoverageEntry {
                interval,
                first_open,
                last_close,
                count,
                expected_count,
                coverage_pct,
            }
        })
        .collect();

    Ok(Json(entries))
}

