use anyhow::Result;
use rusqlite::Connection;

use crate::state::session::SessionActivityState;

pub(crate) fn read(conn: &Connection) -> Result<Option<SessionActivityState>> {
    let mut stmt = conn.prepare(
        "SELECT session_id, actor_id, current_activity, updated_at_epoch_s, session_revision
         FROM session_activity WHERE id = 1",
    )?;

    let result = stmt.query_row([], |row| {
        Ok(SessionActivityState {
            session_id: row.get(0)?,
            actor_id: row.get(1)?,
            current_activity: row.get(2)?,
            updated_at_epoch_s: row.get(3)?,
            session_revision: row.get(4)?,
        })
    });

    match result {
        Ok(state) => Ok(Some(state)),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
        Err(error) => Err(error.into()),
    }
}

pub(crate) fn write(conn: &Connection, state: &SessionActivityState) -> Result<()> {
    conn.execute(
        "INSERT OR REPLACE INTO session_activity
            (id, session_id, actor_id, current_activity, updated_at_epoch_s, session_revision)
         VALUES (1, ?1, ?2, ?3, ?4, ?5)",
        rusqlite::params![
            &state.session_id,
            &state.actor_id,
            &state.current_activity,
            state.updated_at_epoch_s,
            state.session_revision,
        ],
    )?;
    Ok(())
}

pub(crate) fn delete(conn: &Connection) -> Result<()> {
    conn.execute("DELETE FROM session_activity WHERE id = 1", [])?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::db::schema;

    fn test_conn() -> Connection {
        let conn = Connection::open_in_memory().unwrap();
        schema::initialize(&conn, None).unwrap();
        conn
    }

    #[test]
    fn read_returns_none_when_empty() {
        let conn = test_conn();
        assert!(read(&conn).unwrap().is_none());
    }

    #[test]
    fn write_and_read_roundtrip() {
        let conn = test_conn();
        let state = SessionActivityState {
            session_id: "ses_01ABC".to_owned(),
            actor_id: "runtime/worker-1".to_owned(),
            current_activity: "running tests".to_owned(),
            updated_at_epoch_s: 1_000_000,
            session_revision: 2,
        };

        write(&conn, &state).unwrap();
        let loaded = read(&conn).unwrap().expect("should exist");
        assert_eq!(loaded, state);
    }

    #[test]
    fn delete_removes_activity() {
        let conn = test_conn();
        write(
            &conn,
            &SessionActivityState {
                session_id: "ses_01ABC".to_owned(),
                actor_id: "runtime/worker-1".to_owned(),
                current_activity: "running tests".to_owned(),
                updated_at_epoch_s: 1_000_000,
                session_revision: 2,
            },
        )
        .unwrap();

        delete(&conn).unwrap();
        assert!(read(&conn).unwrap().is_none());
    }
}
