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

//! Queries for `k_sol_protocol_candidates`.

/// Inserts one protocol candidate row.
pub async fn query_protocol_candidates_insert(
    database: &crate::Database,
    dto: &crate::ProtocolCandidateDto,
) -> 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 protocol candidate slot '{}' to i64: {}",
                        slot, error
                    )));
                },
            }
        },
        None => None,
    };
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query(
                r#"
INSERT INTO k_sol_protocol_candidates (
    transaction_id,
    instruction_id,
    signature,
    slot,
    program_id,
    program_name_hint,
    candidate_protocol,
    candidate_surface,
    reason,
    evidence_json,
    created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                "#,
            )
            .bind(dto.transaction_id)
            .bind(dto.instruction_id)
            .bind(dto.signature.clone())
            .bind(slot_i64)
            .bind(dto.program_id.clone())
            .bind(dto.program_name_hint.clone())
            .bind(dto.candidate_protocol.clone())
            .bind(dto.candidate_surface.clone())
            .bind(dto.reason.clone())
            .bind(dto.evidence_json.clone())
            .bind(dto.created_at.to_rfc3339())
            .execute(pool)
            .await;
            match query_result {
                Ok(query_result) => return Ok(query_result.last_insert_rowid()),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot insert k_sol_protocol_candidates on sqlite: {}",
                        error
                    )));
                },
            }
        },
    }
}

/// Deletes protocol candidates for one transaction.
///
/// This is useful before recomputing candidates for a replayed transaction.
pub async fn query_protocol_candidates_delete_by_transaction_id(
    database: &crate::Database,
    transaction_id: i64,
) -> Result<u64, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query(
                r#"
DELETE FROM k_sol_protocol_candidates
WHERE transaction_id = ?
                "#,
            )
            .bind(transaction_id)
            .execute(pool)
            .await;
            match query_result {
                Ok(query_result) => return Ok(query_result.rows_affected()),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot delete k_sol_protocol_candidates for transaction_id '{}' on sqlite: {}",
                        transaction_id, error
                    )));
                },
            }
        },
    }
}

/// Lists protocol candidates for one transaction.
pub async fn query_protocol_candidates_list_by_transaction_id(
    database: &crate::Database,
    transaction_id: i64,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query_as::<sqlx::Sqlite, crate::ProtocolCandidateEntity>(
                r#"
SELECT
    id,
    transaction_id,
    instruction_id,
    signature,
    slot,
    program_id,
    program_name_hint,
    candidate_protocol,
    candidate_surface,
    reason,
    evidence_json,
    created_at
FROM k_sol_protocol_candidates
WHERE transaction_id = ?
ORDER BY id ASC
                "#,
            )
            .bind(transaction_id)
            .fetch_all(pool)
            .await;
            let entities = match query_result {
                Ok(entities) => entities,
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list k_sol_protocol_candidates for transaction_id '{}' on sqlite: {}",
                        transaction_id, error
                    )));
                },
            };
            return protocol_candidate_entities_to_dtos(entities);
        },
    }
}

/// Lists protocol candidates for one program id.
pub async fn query_protocol_candidates_list_by_program_id(
    database: &crate::Database,
    program_id: &str,
    limit: u32,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, 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::ProtocolCandidateEntity>(
                r#"
SELECT
    id,
    transaction_id,
    instruction_id,
    signature,
    slot,
    program_id,
    program_name_hint,
    candidate_protocol,
    candidate_surface,
    reason,
    evidence_json,
    created_at
FROM k_sol_protocol_candidates
WHERE program_id = ?
ORDER BY id DESC
LIMIT ?
                "#,
            )
            .bind(program_id.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_protocol_candidates for program_id '{}' on sqlite: {}",
                        program_id, error
                    )));
                },
            };
            return protocol_candidate_entities_to_dtos(entities);
        },
    }
}

/// Lists recent protocol candidates ordered from newest to oldest.
pub async fn query_protocol_candidates_list_recent(
    database: &crate::Database,
    limit: u32,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, 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::ProtocolCandidateEntity>(
                r#"
SELECT
    id,
    transaction_id,
    instruction_id,
    signature,
    slot,
    program_id,
    program_name_hint,
    candidate_protocol,
    candidate_surface,
    reason,
    evidence_json,
    created_at
FROM k_sol_protocol_candidates
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 recent k_sol_protocol_candidates on sqlite: {}",
                        error
                    )));
                },
            };
            return protocol_candidate_entities_to_dtos(entities);
        },
    }
}

fn protocol_candidate_entities_to_dtos(
    entities: std::vec::Vec<crate::ProtocolCandidateEntity>,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, crate::Error> {
    let mut dtos = std::vec::Vec::new();
    for entity in entities {
        let dto_result = crate::ProtocolCandidateDto::try_from(entity);
        let dto = match dto_result {
            Ok(dto) => dto,
            Err(error) => return Err(error),
        };
        dtos.push(dto);
    }
    return Ok(dtos);
}

/// Lists protocol candidate summaries ordered by investigation priority.
pub async fn query_protocol_candidate_summaries_list_by_priority(
    database: &crate::Database,
    limit: u32,
) -> Result<std::vec::Vec<crate::ProtocolCandidateSummaryDto>, 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::ProtocolCandidateSummaryEntity>(
                    r#"
WITH grouped AS (
    SELECT
        program_id,
        program_name_hint,
        candidate_protocol,
        candidate_surface,
        reason,
        COUNT(*) AS occurrence_count,
        COUNT(DISTINCT signature) AS transaction_count,
        MAX(slot) AS last_slot,
        MAX(id) AS latest_candidate_id
    FROM k_sol_protocol_candidates
    GROUP BY
        program_id,
        program_name_hint,
        candidate_protocol,
        candidate_surface,
        reason
)
SELECT
    grouped.program_id,
    grouped.program_name_hint,
    grouped.candidate_protocol,
    grouped.candidate_surface,
    grouped.reason,
    grouped.occurrence_count,
    grouped.transaction_count,
    grouped.last_slot,
    grouped.latest_candidate_id,
    latest.signature AS latest_signature,
    latest.created_at AS latest_created_at
FROM grouped
JOIN k_sol_protocol_candidates latest
    ON latest.id = grouped.latest_candidate_id
ORDER BY
    grouped.transaction_count DESC,
    grouped.occurrence_count DESC,
    grouped.last_slot DESC,
    grouped.latest_candidate_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_protocol_candidates summaries on sqlite: {}",
                        error
                    )));
                },
            };
            let mut dtos = std::vec::Vec::new();
            for entity in entities {
                let dto_result = crate::ProtocolCandidateSummaryDto::try_from(entity);
                let dto = match dto_result {
                    Ok(dto) => dto,
                    Err(error) => return Err(error),
                };
                dtos.push(dto);
            }
            return Ok(dtos);
        },
    }
}
