use anyhow::Result;
use rusqlite::{params, Connection, OptionalExtension};

pub(crate) const KIND_CHECKPOINT: &str = "checkpoint";
pub(crate) const KIND_EVALUATION: &str = "evaluation";

#[derive(Debug, Clone)]
pub(crate) struct RadarCacheEntry {
    pub(crate) kind: &'static str,
    pub(crate) cache_key: String,
    pub(crate) profile: String,
    pub(crate) session_id: Option<String>,
    pub(crate) session_revision: Option<u64>,
    pub(crate) observed_at_epoch_s: u64,
    pub(crate) payload_json: String,
    pub(crate) metadata_json: String,
}

pub(crate) fn read(
    conn: &Connection,
    kind: &'static str,
    cache_key: &str,
) -> Result<Option<RadarCacheEntry>> {
    conn.query_row(
        "SELECT kind, cache_key, profile, session_id, session_revision,
                observed_at_epoch_s, payload_json, metadata_json
         FROM radar_cache_entries
         WHERE kind = ?1 AND cache_key = ?2",
        params![kind, cache_key],
        |row| {
            let row_kind: String = row.get(0)?;
            let kind = match row_kind.as_str() {
                KIND_CHECKPOINT => KIND_CHECKPOINT,
                KIND_EVALUATION => KIND_EVALUATION,
                _ => return Err(rusqlite::Error::InvalidQuery),
            };
            Ok(RadarCacheEntry {
                kind,
                cache_key: row.get(1)?,
                profile: row.get(2)?,
                session_id: row.get(3)?,
                session_revision: row.get(4)?,
                observed_at_epoch_s: row.get(5)?,
                payload_json: row.get(6)?,
                metadata_json: row.get(7)?,
            })
        },
    )
    .optional()
    .map_err(Into::into)
}

pub(crate) fn upsert(conn: &Connection, entry: &RadarCacheEntry) -> Result<()> {
    conn.execute(
        "INSERT INTO radar_cache_entries
            (kind, cache_key, profile, session_id, session_revision,
             observed_at_epoch_s, payload_json, metadata_json)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
         ON CONFLICT(kind, cache_key) DO UPDATE SET
             profile = excluded.profile,
             session_id = excluded.session_id,
             session_revision = excluded.session_revision,
             observed_at_epoch_s = excluded.observed_at_epoch_s,
             payload_json = excluded.payload_json,
             metadata_json = excluded.metadata_json",
        params![
            entry.kind,
            entry.cache_key,
            entry.profile,
            entry.session_id,
            entry.session_revision,
            entry.observed_at_epoch_s,
            entry.payload_json,
            entry.metadata_json,
        ],
    )?;
    Ok(())
}

pub(crate) fn delete_expired(conn: &Connection, older_than_epoch_s: u64) -> Result<()> {
    conn.execute(
        "DELETE FROM radar_cache_entries WHERE observed_at_epoch_s < ?1",
        [older_than_epoch_s],
    )?;
    Ok(())
}

pub(crate) fn delete(conn: &Connection, kind: &'static str, cache_key: &str) -> Result<()> {
    conn.execute(
        "DELETE FROM radar_cache_entries WHERE kind = ?1 AND cache_key = ?2",
        params![kind, cache_key],
    )?;
    Ok(())
}

pub(crate) fn delete_all(conn: &Connection) -> Result<()> {
    conn.execute("DELETE FROM radar_cache_entries", [])?;
    Ok(())
}
