use anyhow::{bail, Result};
use rusqlite::{Connection, Row};

use crate::state::renewal::{RenewalLease, RenewalLeaseStatus, RenewalResetPath};
use crate::state::session::SessionMode;

pub(crate) fn read_pending(conn: &Connection) -> Result<Option<RenewalLease>> {
    let mut stmt = conn.prepare(
        "SELECT renewal_lease_id, thread_id, previous_session_id, previous_session_mode,
                continuation_token, issued_at_epoch_s, expires_at_epoch_s, status,
                fulfilled_session_id, fulfilled_at_epoch_s, reset_path, lifeloop_adapter_id,
                lifeloop_receipt_id, lifecycle_evidence_json
         FROM renewal_leases
         WHERE status = 'pending_reset'",
    )?;
    let leases = stmt
        .query_map([], row_to_lease)?
        .collect::<std::result::Result<Vec<_>, _>>()?;
    match leases.len() {
        0 => Ok(None),
        1 => Ok(leases.into_iter().next()),
        count => bail!("renewal lease invariant violated: {count} pending leases exist"),
    }
}

pub(crate) fn read_by_token(conn: &Connection, token: &str) -> Result<Option<RenewalLease>> {
    read_one(
        conn,
        "SELECT renewal_lease_id, thread_id, previous_session_id, previous_session_mode,
                continuation_token, issued_at_epoch_s, expires_at_epoch_s, status,
                fulfilled_session_id, fulfilled_at_epoch_s, reset_path, lifeloop_adapter_id,
                lifeloop_receipt_id, lifecycle_evidence_json
         FROM renewal_leases
         WHERE continuation_token = ?1",
        [token],
    )
}

fn read_one<P>(conn: &Connection, sql: &str, params: P) -> Result<Option<RenewalLease>>
where
    P: rusqlite::Params,
{
    let result = conn.query_row(sql, params, row_to_lease);

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

fn row_to_lease(row: &Row<'_>) -> rusqlite::Result<RenewalLease> {
    let mode: String = row.get(3)?;
    let previous_session_mode = SessionMode::from_str(&mode).ok_or_else(|| {
        rusqlite::Error::FromSqlConversionFailure(
            3,
            rusqlite::types::Type::Text,
            Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("invalid renewal previous_session_mode `{mode}`"),
            )),
        )
    })?;
    let status: String = row.get(7)?;
    let status = RenewalLeaseStatus::from_str(&status).ok_or_else(|| {
        rusqlite::Error::FromSqlConversionFailure(
            7,
            rusqlite::types::Type::Text,
            Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("invalid renewal lease status `{status}`"),
            )),
        )
    })?;
    let reset_path: String = row.get(10)?;
    let reset_path = RenewalResetPath::from_str(&reset_path).ok_or_else(|| {
        rusqlite::Error::FromSqlConversionFailure(
            10,
            rusqlite::types::Type::Text,
            Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("invalid renewal reset_path `{reset_path}`"),
            )),
        )
    })?;
    Ok(RenewalLease {
        renewal_lease_id: row.get(0)?,
        thread_id: row.get(1)?,
        previous_session_id: row.get(2)?,
        previous_session_mode,
        continuation_token: row.get(4)?,
        issued_at_epoch_s: row.get(5)?,
        expires_at_epoch_s: row.get(6)?,
        status,
        fulfilled_session_id: row.get(8)?,
        fulfilled_at_epoch_s: row.get(9)?,
        reset_path,
        lifeloop_adapter_id: row.get(11)?,
        lifeloop_receipt_id: row.get(12)?,
        lifecycle_evidence_json: row.get(13)?,
    })
}

pub(crate) fn insert(conn: &Connection, lease: &RenewalLease) -> Result<()> {
    conn.execute(
        "INSERT INTO renewal_leases
            (renewal_lease_id, thread_id, previous_session_id, previous_session_mode,
             continuation_token, issued_at_epoch_s, expires_at_epoch_s, status,
             fulfilled_session_id, fulfilled_at_epoch_s, reset_path, lifeloop_adapter_id,
             lifeloop_receipt_id, lifecycle_evidence_json)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
        rusqlite::params![
            lease.renewal_lease_id,
            lease.thread_id,
            lease.previous_session_id,
            lease.previous_session_mode.as_str(),
            lease.continuation_token,
            lease.issued_at_epoch_s,
            lease.expires_at_epoch_s,
            lease.status.as_str(),
            lease.fulfilled_session_id,
            lease.fulfilled_at_epoch_s,
            lease.reset_path.as_str(),
            lease.lifeloop_adapter_id,
            lease.lifeloop_receipt_id,
            lease.lifecycle_evidence_json,
        ],
    )?;
    Ok(())
}

pub(crate) fn fulfill(
    conn: &Connection,
    renewal_lease_id: &str,
    fulfilled_session_id: &str,
    fulfilled_at_epoch_s: u64,
) -> Result<bool> {
    let updated = conn.execute(
        "UPDATE renewal_leases
         SET status = 'fulfilled',
             fulfilled_session_id = ?1,
             fulfilled_at_epoch_s = ?2
         WHERE renewal_lease_id = ?3
           AND status = 'pending_reset'",
        rusqlite::params![fulfilled_session_id, fulfilled_at_epoch_s, renewal_lease_id],
    )?;
    Ok(updated == 1)
}

pub(crate) fn expire_pending_if_needed(
    conn: &Connection,
    now_epoch_s: u64,
) -> Result<Vec<RenewalLease>> {
    let mut expired = Vec::new();
    if let Some(lease) = read_pending(conn)? {
        if lease.expires_at_epoch_s <= now_epoch_s {
            conn.execute(
                "UPDATE renewal_leases
                 SET status = 'expired'
                 WHERE renewal_lease_id = ?1
                   AND status = 'pending_reset'",
                [lease.renewal_lease_id.as_str()],
            )?;
            expired.push(lease);
        }
    }
    Ok(expired)
}
