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

use crate::error::ApiError;
use crate::state::AppState;
use swym_dal::models::ingest_config::{EnrichedIngestConfigRow, IngestConfigRow};
use swym_dal::repo::{ingest_config, instrument};

// -- Request / Response types --

#[derive(Debug, Deserialize)]
pub struct CreateIngestConfigRequest {
    pub exchange: String,
    pub pair: String,
    pub source: Option<String>,
    /// S3 data path variant: `"spot"` (default), `"futures_um"` (USD-M), or `"futures_cm"` (COIN-M).
    pub source_type: Option<String>,
    pub retention_days: Option<i32>,
    /// Candle-specific retention. `null` or absent = unlimited (no purge, full history backfill).
    pub candle_retention_days: Option<i32>,
    pub candle_intervals: Option<Vec<String>>,
}

#[derive(Debug, Deserialize)]
pub struct UpdateIngestConfigRequest {
    pub active: Option<bool>,
    pub retention_days: Option<i32>,
    pub candle_intervals: Option<Vec<String>>,
    /// Set candle retention to a specific number of days.
    pub candle_retention_days: Option<i32>,
    /// Set `true` to explicitly switch candle retention to unlimited (sets the column to NULL).
    pub candle_retention_unlimited: Option<bool>,
    /// Set `true` to reset the ingestion cursor (`last_ingested_date → NULL`), forcing a full
    /// re-ingestion from the current retention window start on the next worker poll.
    /// Use this after increasing `retention_days` to backfill the newly included history.
    pub reset_cursor: Option<bool>,
}

#[derive(Debug, Serialize)]
pub struct IngestConfigResponse {
    pub id: i32,
    pub instrument_id: i32,
    pub source: String,
    pub source_type: String,
    pub retention_days: i32,
    pub candle_retention_days: Option<i32>,
    pub active: bool,
    pub last_ingested_date: Option<NaiveDate>,
    pub candle_intervals: Vec<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl From<IngestConfigRow> for IngestConfigResponse {
    fn from(row: IngestConfigRow) -> Self {
        Self {
            id: row.id,
            instrument_id: row.instrument_id,
            source: row.source,
            source_type: row.source_type,
            retention_days: row.retention_days,
            candle_retention_days: row.candle_retention_days,
            active: row.active,
            last_ingested_date: row.last_ingested_date,
            candle_intervals: row.candle_intervals,
            created_at: row.created_at,
            updated_at: row.updated_at,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct EnrichedIngestConfigResponse {
    pub id: i32,
    pub instrument_id: i32,
    pub source: String,
    pub source_type: String,
    pub retention_days: i32,
    pub candle_retention_days: Option<i32>,
    pub active: bool,
    pub last_ingested_date: Option<NaiveDate>,
    pub candle_intervals: Vec<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub exchange: String,
    pub base_asset: String,
    pub quote_asset: String,
}

impl From<EnrichedIngestConfigRow> for EnrichedIngestConfigResponse {
    fn from(row: EnrichedIngestConfigRow) -> Self {
        Self {
            id: row.id,
            instrument_id: row.instrument_id,
            source: row.source,
            source_type: row.source_type,
            retention_days: row.retention_days,
            candle_retention_days: row.candle_retention_days,
            active: row.active,
            last_ingested_date: row.last_ingested_date,
            candle_intervals: row.candle_intervals,
            created_at: row.created_at,
            updated_at: row.updated_at,
            exchange: row.exchange_name,
            base_asset: row.base_asset,
            quote_asset: row.quote_asset,
        }
    }
}

/// GET /api/v1/ingest-configs
pub async fn list_ingest_configs(
    State(state): State<AppState>,
) -> Result<Json<Vec<EnrichedIngestConfigResponse>>, ApiError> {
    let rows = ingest_config::list_all(&state.pool).await?;
    let responses: Vec<EnrichedIngestConfigResponse> = rows.into_iter().map(Into::into).collect();
    Ok(Json(responses))
}

/// POST /api/v1/ingest-configs
pub async fn create_ingest_config(
    State(state): State<AppState>,
    Json(req): Json<CreateIngestConfigRequest>,
) -> Result<(StatusCode, Json<IngestConfigResponse>), ApiError> {
    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-USDC')",
            req.pair
        )));
    }
    let base = parts[0];
    let quote = parts[1];
    let name_exchange = format!("{}{}", base.to_uppercase(), quote.to_uppercase());

    let source = req.source.as_deref().unwrap_or("binance_s3");
    let source_type = req.source_type.as_deref().unwrap_or("spot");
    if !matches!(source_type, "spot" | "futures_um" | "futures_cm") {
        return Err(ApiError::BadRequest(format!(
            "invalid source_type '{}', expected one of: spot, futures_um, futures_cm",
            source_type
        )));
    }
    let retention_days = req.retention_days.unwrap_or(365);
    let candle_intervals = req.candle_intervals.unwrap_or_default();

    // Map source_type to instrument kind stored in the DB.
    let instrument_kind = if source_type == "spot" { "spot" } else { "perpetual" };

    let db_exchange_id = instrument::ensure_exchange(&state.pool, &req.exchange).await?;
    let db_instrument_id = instrument::ensure_instrument(
        &state.pool,
        db_exchange_id,
        &name_exchange,
        base,
        quote,
        instrument_kind,
    )
    .await?;

    let row = ingest_config::create(
        &state.pool,
        db_instrument_id,
        source,
        source_type,
        retention_days,
        req.candle_retention_days,
        &candle_intervals,
    )
    .await?;
    Ok((StatusCode::CREATED, Json(row.into())))
}

/// PATCH /api/v1/ingest-configs/{id}
pub async fn update_ingest_config(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    Json(req): Json<UpdateIngestConfigRequest>,
) -> Result<Json<IngestConfigResponse>, ApiError> {
    let nothing_to_update = req.active.is_none()
        && req.retention_days.is_none()
        && req.candle_intervals.is_none()
        && req.candle_retention_days.is_none()
        && req.candle_retention_unlimited.is_none()
        && req.reset_cursor.is_none();
    if nothing_to_update {
        return Err(ApiError::BadRequest("nothing to update".to_owned()));
    }
    let candle_intervals = req.candle_intervals.as_deref();
    let row = ingest_config::update(
        &state.pool,
        id,
        req.active,
        req.retention_days,
        candle_intervals,
        req.candle_retention_days,
        req.candle_retention_unlimited,
        req.reset_cursor,
    )
    .await?
    .ok_or_else(|| ApiError::NotFound(format!("ingest config {id} not found")))?;
    Ok(Json(row.into()))
}
