use crate::error::DalError;
use crate::models::ingest_config::{ActiveIngestConfigRow, EnrichedIngestConfigRow, IngestConfigRow};
use chrono::{DateTime, NaiveDate, Utc};
use sqlx::PgPool;

/// All ingest configs (active + inactive), enriched with instrument and exchange info.
pub async fn list_all(pool: &PgPool) -> Result<Vec<EnrichedIngestConfigRow>, DalError> {
    let rows = sqlx::query_as::<_, EnrichedIngestConfigRow>(
        "SELECT ic.id, ic.instrument_id, ic.source, ic.source_type, ic.retention_days,
                ic.candle_retention_days, ic.active, ic.last_ingested_date, ic.candle_intervals,
                ic.created_at, ic.updated_at,
                e.name AS exchange_name, i.base_asset, i.quote_asset
         FROM ingest_configs ic
         JOIN instruments i ON i.id = ic.instrument_id
         JOIN exchanges e ON e.id = i.exchange_id
         ORDER BY ic.id",
    )
    .fetch_all(pool)
    .await?;
    Ok(rows)
}

/// Insert a new ingest config. On conflict (same instrument + source), reactivates
/// the existing row and updates the retention window and candle intervals.
pub async fn create(
    pool: &PgPool,
    instrument_id: i32,
    source: &str,
    source_type: &str,
    retention_days: i32,
    candle_retention_days: Option<i32>,
    candle_intervals: &[String],
) -> Result<IngestConfigRow, DalError> {
    let row = sqlx::query_as::<_, IngestConfigRow>(
        "INSERT INTO ingest_configs (instrument_id, source, source_type, retention_days, candle_retention_days, candle_intervals)
         VALUES ($1, $2, $3, $4, $5, $6)
         ON CONFLICT (instrument_id, source)
         DO UPDATE SET active = true,
                       source_type = EXCLUDED.source_type,
                       retention_days = EXCLUDED.retention_days,
                       candle_retention_days = EXCLUDED.candle_retention_days,
                       candle_intervals = EXCLUDED.candle_intervals,
                       updated_at = now()
         RETURNING id, instrument_id, source, source_type, retention_days, candle_retention_days,
                   active, last_ingested_date, candle_intervals, created_at, updated_at",
    )
    .bind(instrument_id)
    .bind(source)
    .bind(source_type)
    .bind(retention_days)
    .bind(candle_retention_days)
    .bind(candle_intervals)
    .fetch_one(pool)
    .await?;
    Ok(row)
}

/// Update an ingest config's active flag, retention_days, candle_retention_days, and/or candle_intervals.
///
/// `candle_retention_days` sets a specific day count.
/// `candle_retention_unlimited` when `Some(true)` explicitly sets `candle_retention_days` to NULL (unlimited).
/// When both are None the column is left unchanged.
/// `reset_cursor` when `Some(true)` sets `last_ingested_date` to NULL, forcing a full re-ingestion
/// from the current retention window start on the next worker poll (use after expanding retention_days).
/// Returns None if the id does not exist.
pub async fn update(
    pool: &PgPool,
    id: i32,
    active: Option<bool>,
    retention_days: Option<i32>,
    candle_intervals: Option<&[String]>,
    candle_retention_days: Option<i32>,
    candle_retention_unlimited: Option<bool>,
    reset_cursor: Option<bool>,
) -> Result<Option<IngestConfigRow>, DalError> {
    let row = sqlx::query_as::<_, IngestConfigRow>(
        "UPDATE ingest_configs
         SET active = COALESCE($2, active),
             retention_days = COALESCE($3, retention_days),
             candle_intervals = COALESCE($4, candle_intervals),
             candle_retention_days = CASE
                 WHEN $6 = true THEN NULL
                 WHEN $5 IS NOT NULL THEN $5
                 ELSE candle_retention_days
             END,
             last_ingested_date = CASE WHEN $7 = true THEN NULL ELSE last_ingested_date END,
             updated_at = now()
         WHERE id = $1
         RETURNING id, instrument_id, source, source_type, retention_days, candle_retention_days,
                   active, last_ingested_date, candle_intervals, created_at, updated_at",
    )
    .bind(id)
    .bind(active)
    .bind(retention_days)
    .bind(candle_intervals)
    .bind(candle_retention_days)
    .bind(candle_retention_unlimited)
    .bind(reset_cursor)
    .fetch_optional(pool)
    .await?;
    Ok(row)
}

/// All active ingest configs joined with the instrument's exchange symbol.
pub async fn list_active(pool: &PgPool) -> Result<Vec<ActiveIngestConfigRow>, DalError> {
    let rows = sqlx::query_as::<_, ActiveIngestConfigRow>(
        "SELECT ic.id, ic.instrument_id, i.name_exchange AS symbol,
                ic.source, ic.source_type, ic.retention_days, ic.candle_retention_days,
                ic.last_ingested_date, ic.candle_intervals
         FROM ingest_configs ic
         JOIN instruments i ON i.id = ic.instrument_id
         WHERE ic.active = true
         ORDER BY ic.id",
    )
    .fetch_all(pool)
    .await?;
    Ok(rows)
}

/// Advance the ingestion cursor after successfully processing a date's data.
pub async fn update_last_ingested_date(
    pool: &PgPool,
    id: i32,
    date: NaiveDate,
) -> Result<(), DalError> {
    sqlx::query(
        "UPDATE ingest_configs
         SET last_ingested_date = $1, updated_at = now()
         WHERE id = $2",
    )
    .bind(date)
    .bind(id)
    .execute(pool)
    .await?;
    Ok(())
}

/// Delete trades older than `cutoff` for the given instrument.
/// Returns the number of rows deleted.
pub async fn purge_old_trades(
    pool: &PgPool,
    instrument_id: i32,
    cutoff: DateTime<Utc>,
) -> Result<u64, DalError> {
    let result = sqlx::query(
        "DELETE FROM market_trades
         WHERE instrument_id = $1 AND time_exchange < $2",
    )
    .bind(instrument_id)
    .bind(cutoff)
    .execute(pool)
    .await?;
    Ok(result.rows_affected())
}

/// Get the last ingested date for a candle interval, or None if never ingested.
pub async fn get_candle_cursor(
    pool: &PgPool,
    instrument_id: i32,
    interval: &str,
) -> Result<Option<NaiveDate>, DalError> {
    let row: Option<(Option<NaiveDate>,)> = sqlx::query_as(
        "SELECT last_ingested_date FROM candle_ingest_cursors
         WHERE instrument_id = $1 AND interval = $2",
    )
    .bind(instrument_id)
    .bind(interval)
    .fetch_optional(pool)
    .await?;
    Ok(row.and_then(|(d,)| d))
}

/// Upsert the candle ingestion cursor for an instrument + interval.
pub async fn update_candle_cursor(
    pool: &PgPool,
    instrument_id: i32,
    interval: &str,
    date: NaiveDate,
) -> Result<(), DalError> {
    sqlx::query(
        "INSERT INTO candle_ingest_cursors (instrument_id, interval, last_ingested_date)
         VALUES ($1, $2, $3)
         ON CONFLICT (instrument_id, interval)
         DO UPDATE SET last_ingested_date = EXCLUDED.last_ingested_date",
    )
    .bind(instrument_id)
    .bind(interval)
    .bind(date)
    .execute(pool)
    .await?;
    Ok(())
}
