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

//! Queries for `k_sol_dex_decode_replay_ledger`.

/// Inserts or updates one DEX decode replay ledger row.
pub async fn query_dex_decode_replay_ledger_upsert(
    database: &crate::Database,
    dto: &crate::DexDecodeReplayLedgerDto,
) -> Result<i64, crate::Error> {
    let force_replay_required = if dto.force_replay_required { 1_i64 } else { 0_i64 };
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query(
                r#"
INSERT INTO k_sol_dex_decode_replay_ledger (
    transaction_id,
    signature,
    decoder_scope,
    decoder_version,
    decode_status,
    certainty,
    event_count,
    distinct_token_mint_count,
    force_replay_required,
    status_reason,
    created_at,
    updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(transaction_id, decoder_scope, decoder_version) DO UPDATE SET
    signature = excluded.signature,
    decode_status = excluded.decode_status,
    certainty = excluded.certainty,
    event_count = excluded.event_count,
    distinct_token_mint_count = excluded.distinct_token_mint_count,
    force_replay_required = excluded.force_replay_required,
    status_reason = excluded.status_reason,
    updated_at = excluded.updated_at
                "#,
            )
            .bind(dto.transaction_id)
            .bind(dto.signature.clone())
            .bind(dto.decoder_scope.clone())
            .bind(dto.decoder_version.clone())
            .bind(dto.decode_status.clone())
            .bind(dto.certainty.clone())
            .bind(dto.event_count)
            .bind(dto.distinct_token_mint_count)
            .bind(force_replay_required)
            .bind(dto.status_reason.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_dex_decode_replay_ledger on sqlite: {}",
                    error
                )));
            }
            let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
                r#"
SELECT id
FROM k_sol_dex_decode_replay_ledger
WHERE transaction_id = ?
  AND decoder_scope = ?
  AND decoder_version = ?
LIMIT 1
                "#,
            )
            .bind(dto.transaction_id)
            .bind(dto.decoder_scope.clone())
            .bind(dto.decoder_version.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_dex_decode_replay_ledger id for transaction_id '{}' on sqlite: {}",
                        dto.transaction_id, error
                    )));
                },
            }
        },
    }
}

/// Reads one replay ledger row by transaction id, decoder scope, and decoder version.
pub async fn query_dex_decode_replay_ledger_get_by_transaction(
    database: &crate::Database,
    transaction_id: i64,
    decoder_scope: &str,
    decoder_version: &str,
) -> Result<std::option::Option<crate::DexDecodeReplayLedgerDto>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result =
                sqlx::query_as::<sqlx::Sqlite, crate::DexDecodeReplayLedgerEntity>(
                    r#"
SELECT
    id,
    transaction_id,
    signature,
    decoder_scope,
    decoder_version,
    decode_status,
    certainty,
    event_count,
    distinct_token_mint_count,
    force_replay_required,
    status_reason,
    created_at,
    updated_at
FROM k_sol_dex_decode_replay_ledger
WHERE transaction_id = ?
  AND decoder_scope = ?
  AND decoder_version = ?
LIMIT 1
                    "#,
                )
                .bind(transaction_id)
                .bind(decoder_scope.to_string())
                .bind(decoder_version.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_dex_decode_replay_ledger for transaction_id '{}' on sqlite: {}",
                        transaction_id, error
                    )));
                },
            };
            match entity_option {
                Some(entity) => {
                    let dto_result = crate::DexDecodeReplayLedgerDto::try_from(entity);
                    match dto_result {
                        Ok(dto) => return Ok(Some(dto)),
                        Err(error) => return Err(error),
                    }
                },
                None => return Ok(None),
            }
        },
    }
}

/// Reads one replay ledger row by signature, decoder scope, and decoder version.
pub async fn query_dex_decode_replay_ledger_get_by_signature(
    database: &crate::Database,
    signature: &str,
    decoder_scope: &str,
    decoder_version: &str,
) -> Result<std::option::Option<crate::DexDecodeReplayLedgerDto>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result =
                sqlx::query_as::<sqlx::Sqlite, crate::DexDecodeReplayLedgerEntity>(
                    r#"
SELECT
    id,
    transaction_id,
    signature,
    decoder_scope,
    decoder_version,
    decode_status,
    certainty,
    event_count,
    distinct_token_mint_count,
    force_replay_required,
    status_reason,
    created_at,
    updated_at
FROM k_sol_dex_decode_replay_ledger
WHERE signature = ?
  AND decoder_scope = ?
  AND decoder_version = ?
LIMIT 1
                    "#,
                )
                .bind(signature.to_string())
                .bind(decoder_scope.to_string())
                .bind(decoder_version.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_dex_decode_replay_ledger for signature '{}' on sqlite: {}",
                        signature, error
                    )));
                },
            };
            match entity_option {
                Some(entity) => {
                    let dto_result = crate::DexDecodeReplayLedgerDto::try_from(entity);
                    match dto_result {
                        Ok(dto) => return Ok(Some(dto)),
                        Err(error) => return Err(error),
                    }
                },
                None => return Ok(None),
            }
        },
    }
}

#[cfg(test)]
mod tests {
    async fn make_database() -> crate::Database {
        let tempdir = tempfile::tempdir().expect("tempdir must succeed");
        let database_path = tempdir.path().join("dex_decode_replay_ledger.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");
    }

    async fn insert_chain_transaction(database: &crate::Database) -> i64 {
        let slot_dto = crate::ChainSlotDto::new(777, Some(776), Some(1_700_000_007));
        crate::query_chain_slots_upsert(database, &slot_dto)
            .await
            .expect("slot upsert must succeed");
        let dto = crate::ChainTransactionDto::new(
            "ledger_sig".to_string(),
            Some(777),
            Some(1_700_000_007),
            Some("test".to_string()),
            None,
            None,
            Some("{}".to_string()),
            "{}".to_string(),
        );
        return crate::query_chain_transactions_upsert(database, &dto)
            .await
            .expect("transaction upsert must succeed");
    }

    #[tokio::test]
    async fn dex_decode_replay_ledger_roundtrip_works() {
        let database = make_database().await;
        let transaction_id = insert_chain_transaction(&database).await;
        let dto = crate::DexDecodeReplayLedgerDto::new(
            transaction_id,
            "ledger_sig".to_string(),
            "dex_decode.local_pipeline".to_string(),
            "test-version".to_string(),
            crate::DexDecodeReplayLedgerDto::STATUS_DECODED.to_string(),
            crate::DexDecodeReplayLedgerDto::CERTAINTY_SURE.to_string(),
            1,
            2,
            false,
            Some("single-pair decode completed".to_string()),
        );
        let upsert_id = crate::query_dex_decode_replay_ledger_upsert(&database, &dto)
            .await
            .expect("ledger upsert must succeed");
        assert!(upsert_id > 0);
        let fetched = crate::query_dex_decode_replay_ledger_get_by_signature(
            &database,
            "ledger_sig",
            "dex_decode.local_pipeline",
            "test-version",
        )
        .await
        .expect("ledger fetch must succeed")
        .expect("ledger row must exist");
        assert_eq!(fetched.transaction_id, transaction_id);
        assert!(fetched.can_skip_decode());
    }
}
