// file: kb_lib/src/db/queries/chain_slot.rs

//! Queries for `k_sol_chain_slots`.

/// Inserts or updates one normalized chain slot row.
pub async fn query_chain_slots_upsert(
    database: &crate::Database,
    dto: &crate::ChainSlotDto,
) -> Result<u64, crate::Error> {
    let slot_result = i64::try_from(dto.slot);
    let slot = match slot_result {
        Ok(slot) => slot,
        Err(error) => {
            return Err(crate::Error::Db(format!(
                "cannot convert chain slot '{}' to i64: {}",
                dto.slot, error
            )));
        },
    };
    let parent_slot = match dto.parent_slot {
        Some(parent_slot) => {
            let parent_slot_result = i64::try_from(parent_slot);
            match parent_slot_result {
                Ok(parent_slot) => Some(parent_slot),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot convert chain parent_slot '{}' to i64: {}",
                        parent_slot, error
                    )));
                },
            }
        },
        None => None,
    };
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query(
                r#"
INSERT INTO k_sol_chain_slots (
    slot,
    parent_slot,
    block_time_unix,
    created_at,
    updated_at
)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(slot) DO UPDATE SET
    parent_slot = excluded.parent_slot,
    block_time_unix = excluded.block_time_unix,
    updated_at = excluded.updated_at
                "#,
            )
            .bind(slot)
            .bind(parent_slot)
            .bind(dto.block_time_unix)
            .bind(dto.created_at.to_rfc3339())
            .bind(dto.updated_at.to_rfc3339())
            .execute(pool)
            .await;
            if let Err(error) = query_result {
                return Err(crate::Error::Db(format!(
                    "cannot upsert k_sol_chain_slots on sqlite: {}",
                    error
                )));
            }
            return Ok(dto.slot);
        },
    }
}

/// Reads one chain slot row by slot number.
pub async fn query_chain_slots_get(
    database: &crate::Database,
    slot: u64,
) -> Result<std::option::Option<crate::ChainSlotDto>, crate::Error> {
    let slot_result = i64::try_from(slot);
    let slot_i64 = match slot_result {
        Ok(slot_i64) => slot_i64,
        Err(error) => {
            return Err(crate::Error::Db(format!(
                "cannot convert requested chain slot '{}' to i64: {}",
                slot, error
            )));
        },
    };
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query_as::<sqlx::Sqlite, crate::ChainSlotEntity>(
                r#"
SELECT
    slot,
    parent_slot,
    block_time_unix,
    created_at,
    updated_at
FROM k_sol_chain_slots
WHERE slot = ?
LIMIT 1
                "#,
            )
            .bind(slot_i64)
            .fetch_optional(pool)
            .await;
            let entity_option = match query_result {
                Ok(entity_option) => entity_option,
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot fetch k_sol_chain_slots for slot '{}' on sqlite: {}",
                        slot, error
                    )));
                },
            };
            match entity_option {
                Some(entity) => {
                    let dto_result = crate::ChainSlotDto::try_from(entity);
                    match dto_result {
                        Ok(dto) => return Ok(Some(dto)),
                        Err(error) => return Err(error),
                    }
                },
                None => return Ok(None),
            }
        },
    }
}

/// Lists recent chain slots ordered from newest to oldest.
pub async fn query_chain_slots_list_recent(
    database: &crate::Database,
    limit: u32,
) -> Result<std::vec::Vec<crate::ChainSlotDto>, crate::Error> {
    if limit == 0 {
        return Ok(std::vec::Vec::new());
    }
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query_as::<sqlx::Sqlite, crate::ChainSlotEntity>(
                r#"
SELECT
    slot,
    parent_slot,
    block_time_unix,
    created_at,
    updated_at
FROM k_sol_chain_slots
ORDER BY slot DESC
LIMIT ?
                "#,
            )
            .bind(i64::from(limit))
            .fetch_all(pool)
            .await;
            let entities = match query_result {
                Ok(entities) => entities,
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list k_sol_chain_slots on sqlite: {}",
                        error
                    )));
                },
            };
            let mut dtos = std::vec::Vec::new();
            for entity in entities {
                let dto_result = crate::ChainSlotDto::try_from(entity);
                let dto = match dto_result {
                    Ok(dto) => dto,
                    Err(error) => return Err(error),
                };
                dtos.push(dto);
            }
            return Ok(dtos);
        },
    }
}

#[cfg(test)]
mod tests {
    async fn make_database() -> crate::Database {
        let tempdir = tempfile::tempdir().expect("tempdir must succeed");
        let database_path = tempdir.path().join("chain_slot.sqlite3");
        let config = crate::DatabaseConfig {
            enabled: true,
            backend: crate::DatabaseBackend::Sqlite,
            sqlite: crate::SqliteDatabaseConfig {
                path: database_path.to_string_lossy().to_string(),
                create_if_missing: true,
                busy_timeout_ms: 5000,
                max_connections: 1,
                auto_initialize_schema: true,
                use_wal: true,
            },
        };
        return crate::Database::connect_and_initialize(&config)
            .await
            .expect("database init must succeed");
    }

    #[tokio::test]
    async fn chain_slot_roundtrip_works() {
        let database = make_database().await;
        let dto = crate::ChainSlotDto::new(424242, Some(424241), Some(1_777_777_777));
        let slot = crate::query_chain_slots_upsert(&database, &dto)
            .await
            .expect("chain slot upsert must succeed");
        assert_eq!(slot, 424242);
        let fetched = crate::query_chain_slots_get(&database, 424242)
            .await
            .expect("chain slot fetch must succeed")
            .expect("chain slot must exist");
        assert_eq!(fetched.slot, 424242);
        assert_eq!(fetched.parent_slot, Some(424241));
        assert_eq!(fetched.block_time_unix, Some(1_777_777_777));
        let listed = crate::query_chain_slots_list_recent(&database, 10)
            .await
            .expect("chain slot list must succeed");
        assert_eq!(listed.len(), 1);
        assert_eq!(listed[0].slot, 424242);
        assert_eq!(listed[0].parent_slot, Some(424241));
        assert_eq!(listed[0].block_time_unix, Some(1_777_777_777));
    }
}
