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

//! Queries for `k_sol_chain_transactions`.

/// Inserts or updates one normalized chain transaction row.
pub async fn query_chain_transactions_upsert(
    database: &crate::Database,
    dto: &crate::ChainTransactionDto,
) -> Result<i64, crate::Error> {
    let slot_i64 = match dto.slot {
        Some(slot) => {
            let slot_result = i64::try_from(slot);
            match slot_result {
                Ok(slot) => Some(slot),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot convert chain transaction slot '{}' to i64: {}",
                        slot, error
                    )));
                },
            }
        },
        None => None,
    };
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query(
                r#"
INSERT INTO k_sol_chain_transactions (
    signature,
    slot,
    block_time_unix,
    source_endpoint_name,
    version_text,
    err_json,
    meta_json,
    transaction_json,
    created_at,
    updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(signature) DO UPDATE SET
    slot = excluded.slot,
    block_time_unix = excluded.block_time_unix,
    source_endpoint_name = excluded.source_endpoint_name,
    version_text = excluded.version_text,
    err_json = excluded.err_json,
    meta_json = excluded.meta_json,
    transaction_json = excluded.transaction_json,
    updated_at = excluded.updated_at
                "#,
            )
            .bind(dto.signature.clone())
            .bind(slot_i64)
            .bind(dto.block_time_unix)
            .bind(dto.source_endpoint_name.clone())
            .bind(dto.version_text.clone())
            .bind(dto.err_json.clone())
            .bind(dto.meta_json.clone())
            .bind(dto.transaction_json.clone())
            .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_transactions on sqlite: {}",
                    error
                )));
            }
            let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
                r#"
SELECT id
FROM k_sol_chain_transactions
WHERE signature = ?
LIMIT 1
                "#,
            )
            .bind(dto.signature.clone())
            .fetch_one(pool)
            .await;
            match id_result {
                Ok(id) => return Ok(id),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot fetch k_sol_chain_transactions id for signature '{}' on sqlite: {}",
                        dto.signature, error
                    )));
                },
            }
        },
    }
}

/// Reads one chain transaction row by signature.
pub async fn query_chain_transactions_get_by_signature(
    database: &crate::Database,
    signature: &str,
) -> Result<std::option::Option<crate::ChainTransactionDto>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query_as::<sqlx::Sqlite, crate::ChainTransactionEntity>(
                r#"
SELECT
    id,
    signature,
    slot,
    block_time_unix,
    source_endpoint_name,
    version_text,
    err_json,
    meta_json,
    transaction_json,
    created_at,
    updated_at
FROM k_sol_chain_transactions
WHERE signature = ?
LIMIT 1
                "#,
            )
            .bind(signature.to_string())
            .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_transactions for signature '{}' on sqlite: {}",
                        signature, error
                    )));
                },
            };
            match entity_option {
                Some(entity) => {
                    let dto_result = crate::ChainTransactionDto::try_from(entity);
                    match dto_result {
                        Ok(dto) => return Ok(Some(dto)),
                        Err(error) => return Err(error),
                    }
                },
                None => return Ok(None),
            }
        },
    }
}

/// Lists recent chain transactions ordered from newest to oldest.
pub async fn query_chain_transactions_list_recent(
    database: &crate::Database,
    limit: u32,
) -> Result<std::vec::Vec<crate::ChainTransactionDto>, 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::ChainTransactionEntity>(
                r#"
SELECT
    id,
    signature,
    slot,
    block_time_unix,
    source_endpoint_name,
    version_text,
    err_json,
    meta_json,
    transaction_json,
    created_at,
    updated_at
FROM k_sol_chain_transactions
ORDER BY id 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_transactions on sqlite: {}",
                        error
                    )));
                },
            };
            let mut dtos = std::vec::Vec::new();
            for entity in entities {
                let dto_result = crate::ChainTransactionDto::try_from(entity);
                let dto = match dto_result {
                    Ok(dto) => dto,
                    Err(error) => return Err(error),
                };
                dtos.push(dto);
            }
            return Ok(dtos);
        },
    }
}

/// Lists persisted chain transaction signatures for local pipeline replay.
pub async fn query_chain_transactions_list_signatures_for_replay(
    database: &crate::Database,
    limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let effective_limit = match limit {
                Some(limit) => {
                    if limit <= 0 {
                        10_000
                    } else {
                        limit
                    }
                },
                None => 10_000,
            };
            let query_result = sqlx::query_scalar::<sqlx::Sqlite, std::string::String>(
                r#"
SELECT signature
FROM k_sol_chain_transactions
ORDER BY id ASC
LIMIT ?
                "#,
            )
            .bind(effective_limit)
            .fetch_all(pool)
            .await;
            match query_result {
                Ok(signatures) => return Ok(signatures),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list k_sol_chain_transactions signatures for local replay on sqlite: {}",
                        error
                    )));
                },
            }
        },
    }
}

#[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_transaction.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_transaction_roundtrip_works() {
        let database = make_database().await;
        let slot_dto = crate::ChainSlotDto::new(515151, Some(515150), Some(1_700_000_001));
        crate::query_chain_slots_upsert(&database, &slot_dto)
            .await
            .expect("chain slot upsert must succeed");
        let dto = crate::ChainTransactionDto::new(
            "sig-chain-transaction-1".to_string(),
            Some(515151),
            Some(1_700_000_001),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            Some(r#"{"fee":5000}"#.to_string()),
            r#"{"slot":515151,"transaction":{"message":{"instructions":[]}}}"#.to_string(),
        );
        let transaction_id = crate::query_chain_transactions_upsert(&database, &dto)
            .await
            .expect("chain transaction upsert must succeed");
        assert!(transaction_id > 0);
        let fetched =
            crate::query_chain_transactions_get_by_signature(&database, "sig-chain-transaction-1")
                .await
                .expect("chain transaction fetch must succeed")
                .expect("chain transaction must exist");
        assert_eq!(fetched.id, Some(transaction_id));
        assert_eq!(fetched.signature, "sig-chain-transaction-1");
        assert_eq!(fetched.slot, Some(515151));
        assert_eq!(fetched.block_time_unix, Some(1_700_000_001));
        assert_eq!(fetched.source_endpoint_name, Some("helius_primary_http".to_string()));
        assert_eq!(fetched.version_text, Some("0".to_string()));
        assert_eq!(fetched.meta_json, Some(r#"{"fee":5000}"#.to_string()));
        let listed = crate::query_chain_transactions_list_recent(&database, 10)
            .await
            .expect("chain transaction list must succeed");
        assert_eq!(listed.len(), 1);
        assert_eq!(listed[0].signature, "sig-chain-transaction-1");
        assert_eq!(listed[0].slot, Some(515151));
    }
}
