use rusqlite::Connection;

use crate::core::error::Result;
use crate::core::metrics::StageTiming;
use crate::core::time::now_ms;

pub struct SyncRunRecorder {
    row_id: i64,
}

impl SyncRunRecorder {
    pub fn start(conn: &Connection, command: &str, run_id: &str) -> Result<Self> {
        let now = now_ms();
        conn.execute(
            "INSERT INTO sync_runs (started_at, heartbeat_at, status, command, run_id)
             VALUES (?1, ?2, 'running', ?3, ?4)",
            rusqlite::params![now, now, command, run_id],
        )?;
        let row_id = conn.last_insert_rowid();
        Ok(Self { row_id })
    }

    /// Returns the database row ID of this sync run.
    pub fn row_id(&self) -> i64 {
        self.row_id
    }

    /// Sets surgical-mode metadata on the run (mode, phase, IID manifest).
    pub fn set_surgical_metadata(
        &self,
        conn: &Connection,
        mode: &str,
        phase: &str,
        surgical_iids_json: &str,
    ) -> Result<()> {
        conn.execute(
            "UPDATE sync_runs
             SET mode = ?1, phase = ?2, surgical_iids_json = ?3
             WHERE id = ?4",
            rusqlite::params![mode, phase, surgical_iids_json, self.row_id],
        )?;
        Ok(())
    }

    /// Updates the current phase and refreshes the heartbeat timestamp.
    pub fn update_phase(&self, conn: &Connection, phase: &str) -> Result<()> {
        let now = now_ms();
        conn.execute(
            "UPDATE sync_runs SET phase = ?1, heartbeat_at = ?2 WHERE id = ?3",
            rusqlite::params![phase, now, self.row_id],
        )?;
        Ok(())
    }

    /// Increments a counter column by 1 based on entity type and stage.
    /// Unknown (entity_type, stage) combinations are silently ignored.
    pub fn record_entity_result(
        &self,
        conn: &Connection,
        entity_type: &str,
        stage: &str,
    ) -> Result<()> {
        let column = match (entity_type, stage) {
            ("issue", "fetched") => "issues_fetched",
            ("issue", "ingested") => "issues_ingested",
            ("mr", "fetched") => "mrs_fetched",
            ("mr", "ingested") => "mrs_ingested",
            ("issue" | "mr", "skipped_stale") => "skipped_stale",
            ("doc", "regenerated") => "docs_regenerated",
            ("doc", "embedded") => "docs_embedded",
            (_, "warning") => "warnings_count",
            _ => return Ok(()),
        };
        // Column name is from a hardcoded match, not user input — safe to interpolate.
        let sql = format!("UPDATE sync_runs SET {column} = {column} + 1 WHERE id = ?1");
        conn.execute(&sql, rusqlite::params![self.row_id])?;
        Ok(())
    }

    /// Marks the run as cancelled with a reason. Consumes self (terminal state).
    pub fn cancel(self, conn: &Connection, reason: &str) -> Result<()> {
        let now = now_ms();
        conn.execute(
            "UPDATE sync_runs
             SET status = 'cancelled', error = ?1, cancelled_at = ?2, finished_at = ?3
             WHERE id = ?4",
            rusqlite::params![reason, now, now, self.row_id],
        )?;
        Ok(())
    }

    pub fn succeed(
        self,
        conn: &Connection,
        metrics: &[StageTiming],
        total_items: usize,
        total_errors: usize,
    ) -> Result<()> {
        let now = now_ms();
        let metrics_json = serde_json::to_string(metrics).unwrap_or_else(|_| "[]".to_string());
        conn.execute(
            "UPDATE sync_runs
             SET finished_at = ?1, status = 'succeeded',
                 metrics_json = ?2, total_items_processed = ?3, total_errors = ?4
             WHERE id = ?5",
            rusqlite::params![
                now,
                metrics_json,
                total_items as i64,
                total_errors as i64,
                self.row_id
            ],
        )?;
        Ok(())
    }

    pub fn fail(
        self,
        conn: &Connection,
        error: &str,
        metrics: Option<&[StageTiming]>,
    ) -> Result<()> {
        let now = now_ms();
        let metrics_json =
            metrics.map(|m| serde_json::to_string(m).unwrap_or_else(|_| "[]".to_string()));
        conn.execute(
            "UPDATE sync_runs
             SET finished_at = ?1, status = 'failed', error = ?2,
                 metrics_json = ?3
             WHERE id = ?4",
            rusqlite::params![now, error, metrics_json, self.row_id],
        )?;
        Ok(())
    }
}

#[cfg(test)]
#[path = "sync_run_tests.rs"]
mod tests;
