use crate::error::DalError;
use crate::models::condition_audit::ConditionAuditRow;
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use uuid::Uuid;

/// Lightweight insert payload (no DB-generated ID).
pub struct InsertAuditRow {
    pub bar_index: i32,
    pub bar_time: DateTime<Utc>,
    pub rule_index: i16,
    pub rule_fired: bool,
    /// Pre-serialised JSON array of `ConditionAudit` objects.
    pub conditions: serde_json::Value,
}

/// Maximum rows per INSERT statement (6 params each; PostgreSQL limit is 65535 total).
const INSERT_CHUNK_SIZE: usize = 10_000;

/// Bulk-insert condition audit rows for a run, chunked to stay within PostgreSQL's
/// 65535 bind-parameter limit.
pub async fn insert_batch(
    pool: &PgPool,
    run_id: Uuid,
    rows: &[InsertAuditRow],
) -> Result<(), DalError> {
    for chunk in rows.chunks(INSERT_CHUNK_SIZE) {
        let mut query = String::from(
            "INSERT INTO paper_run_condition_audit \
             (run_id, bar_index, bar_time, rule_index, rule_fired, conditions) VALUES ",
        );

        for i in 0..chunk.len() {
            let base = i * 6;
            if i > 0 {
                query.push(',');
            }
            query.push_str(&format!(
                "(${}, ${}, ${}, ${}, ${}, ${})",
                base + 1, base + 2, base + 3, base + 4, base + 5, base + 6,
            ));
        }

        let mut q = sqlx::query(&query);
        for r in chunk {
            q = q
                .bind(run_id)
                .bind(r.bar_index)
                .bind(r.bar_time)
                .bind(r.rule_index)
                .bind(r.rule_fired)
                .bind(&r.conditions);
        }

        q.execute(pool).await?;
    }
    Ok(())
}

/// Returns `true` if any condition audit rows exist for this run.
pub async fn has_audit(pool: &PgPool, run_id: Uuid) -> Result<bool, DalError> {
    let (exists,): (bool,) = sqlx::query_as(
        "SELECT EXISTS(SELECT 1 FROM paper_run_condition_audit WHERE run_id = $1)",
    )
    .bind(run_id)
    .fetch_one(pool)
    .await?;
    Ok(exists)
}

/// Paginated fetch of raw audit rows, ordered by bar_index then rule_index.
pub async fn list_detail_page(
    pool: &PgPool,
    run_id: Uuid,
    offset: i64,
    limit: i64,
) -> Result<Vec<ConditionAuditRow>, DalError> {
    let rows = sqlx::query_as::<_, ConditionAuditRow>(
        "SELECT id, run_id, bar_index, bar_time, rule_index, rule_fired, conditions \
         FROM paper_run_condition_audit \
         WHERE run_id = $1 \
         ORDER BY bar_index, rule_index \
         LIMIT $2 OFFSET $3",
    )
    .bind(run_id)
    .bind(limit)
    .bind(offset)
    .fetch_all(pool)
    .await?;
    Ok(rows)
}

/// Count total audit rows for a run.
pub async fn count_by_run(pool: &PgPool, run_id: Uuid) -> Result<i64, DalError> {
    let (count,): (i64,) =
        sqlx::query_as("SELECT COUNT(*) FROM paper_run_condition_audit WHERE run_id = $1")
            .bind(run_id)
            .fetch_one(pool)
            .await?;
    Ok(count)
}
