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

//! Queries for `k_sol_instruction_observations`.

/// Upserts one instruction observation row.
pub async fn query_instruction_observations_upsert(
    database: &crate::Database,
    dto: &crate::InstructionObservationDto,
) -> Result<i64, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query(
                r#"
INSERT INTO k_sol_instruction_observations (
    observation_key,
    transaction_id,
    signature,
    slot,
    block_time,
    failed,
    instruction_id,
    parent_instruction_id,
    instruction_index,
    inner_instruction_index,
    program_id,
    decoder_code,
    discriminator_hex,
    instruction_name,
    accounts_json,
    data_json,
    pool_account,
    decoded_event_kind,
    decoded_event_id,
    observed_at,
    updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(observation_key) DO UPDATE SET
    transaction_id = excluded.transaction_id,
    signature = excluded.signature,
    slot = excluded.slot,
    block_time = excluded.block_time,
    failed = excluded.failed,
    instruction_id = excluded.instruction_id,
    parent_instruction_id = excluded.parent_instruction_id,
    instruction_index = excluded.instruction_index,
    inner_instruction_index = excluded.inner_instruction_index,
    program_id = excluded.program_id,
    decoder_code = excluded.decoder_code,
    discriminator_hex = excluded.discriminator_hex,
    instruction_name = excluded.instruction_name,
    accounts_json = excluded.accounts_json,
    data_json = excluded.data_json,
    pool_account = excluded.pool_account,
    decoded_event_kind = excluded.decoded_event_kind,
    decoded_event_id = excluded.decoded_event_id,
    updated_at = excluded.updated_at
                "#,
            )
            .bind(dto.observation_key.clone())
            .bind(dto.transaction_id)
            .bind(dto.signature.clone())
            .bind(dto.slot)
            .bind(dto.block_time)
            .bind(if dto.failed { 1_i64 } else { 0_i64 })
            .bind(dto.instruction_id)
            .bind(dto.parent_instruction_id)
            .bind(dto.instruction_index)
            .bind(dto.inner_instruction_index)
            .bind(dto.program_id.clone())
            .bind(dto.decoder_code.clone())
            .bind(dto.discriminator_hex.clone())
            .bind(dto.instruction_name.clone())
            .bind(dto.accounts_json.clone())
            .bind(dto.data_json.clone())
            .bind(dto.pool_account.clone())
            .bind(dto.decoded_event_kind.clone())
            .bind(dto.decoded_event_id)
            .bind(dto.observed_at.to_rfc3339())
            .bind(dto.updated_at.to_rfc3339())
            .execute(pool)
            .await;
            let query_result = match query_result {
                Ok(query_result) => query_result,
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot upsert k_sol_instruction_observations on sqlite: {}",
                        error
                    )));
                },
            };
            return Ok(query_result.last_insert_rowid());
        },
    }
}

/// Lists instruction observations by optional decoder/discriminator/instruction filters.
pub async fn query_instruction_observations_list_by_filter(
    database: &crate::Database,
    decoder_code: std::option::Option<&str>,
    discriminator_hex: std::option::Option<&str>,
    instruction_name: std::option::Option<&str>,
    limit: u32,
) -> Result<std::vec::Vec<crate::InstructionObservationDto>, 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::InstructionObservationEntity>(
                r#"
SELECT
    id,
    observation_key,
    transaction_id,
    signature,
    slot,
    block_time,
    failed,
    instruction_id,
    parent_instruction_id,
    instruction_index,
    inner_instruction_index,
    program_id,
    decoder_code,
    discriminator_hex,
    instruction_name,
    accounts_json,
    data_json,
    pool_account,
    decoded_event_kind,
    decoded_event_id,
    observed_at,
    updated_at
FROM k_sol_instruction_observations
WHERE (? IS NULL OR decoder_code = ?)
  AND (? IS NULL OR discriminator_hex = ?)
  AND (? IS NULL OR instruction_name = ?)
ORDER BY slot DESC, transaction_id DESC, instruction_id ASC
LIMIT ?
                "#,
            )
            .bind(decoder_code.map(str::to_string))
            .bind(decoder_code.map(str::to_string))
            .bind(discriminator_hex.map(str::to_string))
            .bind(discriminator_hex.map(str::to_string))
            .bind(instruction_name.map(str::to_string))
            .bind(instruction_name.map(str::to_string))
            .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_instruction_observations on sqlite: {}",
                        error
                    )));
                },
            };
            let mut dtos = std::vec::Vec::new();
            for entity in entities {
                let dto_result = crate::InstructionObservationDto::try_from(entity);
                let dto = match dto_result {
                    Ok(dto) => dto,
                    Err(error) => return Err(error),
                };
                dtos.push(dto);
            }
            return Ok(dtos);
        },
    }
}

/// Deletes instruction observations for a set of transaction ids before rebuilding the technical index.
pub async fn query_instruction_observations_delete_by_transaction_ids(
    database: &crate::Database,
    transaction_ids: &[i64],
) -> Result<u64, crate::Error> {
    if transaction_ids.is_empty() {
        return Ok(0);
    }
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let mut deleted_count: u64 = 0;
            for transaction_id_chunk in transaction_ids.chunks(900) {
                let mut query_builder = sqlx::QueryBuilder::<sqlx::Sqlite>::new(
                    "DELETE FROM k_sol_instruction_observations WHERE transaction_id IN (",
                );
                let mut separated = query_builder.separated(", ");
                for transaction_id in transaction_id_chunk {
                    separated.push_bind(*transaction_id);
                }
                separated.push_unseparated(")");
                let query = query_builder.build();
                let query_result = query.execute(pool).await;
                match query_result {
                    Ok(query_result) => {
                        deleted_count = deleted_count.saturating_add(query_result.rows_affected());
                    },
                    Err(error) => {
                        return Err(crate::Error::Db(format!(
                            "cannot delete k_sol_instruction_observations by transaction ids on sqlite: {}",
                            error
                        )));
                    },
                }
            }
            return Ok(deleted_count);
        },
    }
}

/// Lists instruction-observation source rows for one transaction signature.
pub async fn query_instruction_observation_source_rows_list_by_signature(
    database: &crate::Database,
    signature: &str,
) -> Result<std::vec::Vec<crate::InstructionObservationSourceRow>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result =
                sqlx::query_as::<sqlx::Sqlite, crate::InstructionObservationSourceRow>(
                    r#"
SELECT
    tx.id AS transaction_id,
    tx.signature AS signature,
    tx.slot AS slot,
    tx.block_time_unix AS block_time,
    tx.err_json AS err_json,
    ins.id AS instruction_id,
    ins.parent_instruction_id AS parent_instruction_id,
    ins.instruction_index AS instruction_index,
    ins.inner_instruction_index AS inner_instruction_index,
    ins.program_id AS program_id,
    ins.accounts_json AS accounts_json,
    ins.data_json AS data_json,
    de.pool_account AS pool_account,
    de.event_kind AS decoded_event_kind,
    de.id AS decoded_event_id
FROM k_sol_chain_instructions ins
JOIN k_sol_chain_transactions tx
    ON tx.id = ins.transaction_id
LEFT JOIN k_sol_dex_decoded_events de
    ON de.transaction_id = tx.id
   AND de.instruction_id = ins.id
WHERE tx.signature = ?
ORDER BY ins.instruction_index ASC, ins.inner_instruction_index ASC, ins.id ASC
                "#,
                )
                .bind(signature.to_string())
                .fetch_all(pool)
                .await;
            match query_result {
                Ok(rows) => return Ok(rows),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list instruction observation source rows for signature '{}': {}",
                        signature, error
                    )));
                },
            }
        },
    }
}

/// Lists instruction-observation source rows for the local replay window.
pub async fn query_instruction_observation_source_rows_list_replay_window(
    database: &crate::Database,
    limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<crate::InstructionObservationSourceRow>, crate::Error> {
    let effective_limit = match limit {
        Some(limit) => {
            if limit <= 0 {
                10_000
            } else {
                limit
            }
        },
        None => 10_000,
    };
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result =
                sqlx::query_as::<sqlx::Sqlite, crate::InstructionObservationSourceRow>(
                    r#"
WITH replay_transactions AS (
    SELECT id
    FROM k_sol_chain_transactions
    ORDER BY id ASC
    LIMIT ?
)
SELECT
    tx.id AS transaction_id,
    tx.signature AS signature,
    tx.slot AS slot,
    tx.block_time_unix AS block_time,
    tx.err_json AS err_json,
    ins.id AS instruction_id,
    ins.parent_instruction_id AS parent_instruction_id,
    ins.instruction_index AS instruction_index,
    ins.inner_instruction_index AS inner_instruction_index,
    ins.program_id AS program_id,
    ins.accounts_json AS accounts_json,
    ins.data_json AS data_json,
    de.pool_account AS pool_account,
    de.event_kind AS decoded_event_kind,
    de.id AS decoded_event_id
FROM k_sol_chain_instructions ins
JOIN replay_transactions replay_tx
    ON replay_tx.id = ins.transaction_id
JOIN k_sol_chain_transactions tx
    ON tx.id = ins.transaction_id
LEFT JOIN k_sol_dex_decoded_events de
    ON de.transaction_id = tx.id
   AND de.instruction_id = ins.id
ORDER BY tx.id ASC, ins.instruction_index ASC, ins.inner_instruction_index ASC, ins.id ASC
                "#,
                )
                .bind(effective_limit)
                .fetch_all(pool)
                .await;
            match query_result {
                Ok(rows) => return Ok(rows),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list instruction observation source rows for replay window: {}",
                        error
                    )));
                },
            }
        },
    }
}

/// Lists recent instruction-observation source rows.
pub async fn query_instruction_observation_source_rows_list_recent(
    database: &crate::Database,
    limit: u32,
) -> Result<std::vec::Vec<crate::InstructionObservationSourceRow>, 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::InstructionObservationSourceRow>(
                    r#"
SELECT
    tx.id AS transaction_id,
    tx.signature AS signature,
    tx.slot AS slot,
    tx.block_time_unix AS block_time,
    tx.err_json AS err_json,
    ins.id AS instruction_id,
    ins.parent_instruction_id AS parent_instruction_id,
    ins.instruction_index AS instruction_index,
    ins.inner_instruction_index AS inner_instruction_index,
    ins.program_id AS program_id,
    ins.accounts_json AS accounts_json,
    ins.data_json AS data_json,
    de.pool_account AS pool_account,
    de.event_kind AS decoded_event_kind,
    de.id AS decoded_event_id
FROM k_sol_chain_instructions ins
JOIN k_sol_chain_transactions tx
    ON tx.id = ins.transaction_id
LEFT JOIN k_sol_dex_decoded_events de
    ON de.transaction_id = tx.id
   AND de.instruction_id = ins.id
ORDER BY ins.id DESC
LIMIT ?
                "#,
                )
                .bind(i64::from(limit))
                .fetch_all(pool)
                .await;
            match query_result {
                Ok(rows) => return Ok(rows),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list recent instruction observation source rows: {}",
                        error
                    )));
                },
            }
        },
    }
}
