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

//! Queries for `k_sol_trade_events`.

/// Deletes all local market materialization rows that are rebuilt from trade events.
///
/// The local replay pipeline is deterministic over persisted raw transactions. Clearing
/// these derived tables before replay prevents stale failed-transaction trades, metrics
/// or candles from surviving after actionability rules change.
pub async fn query_trade_market_materialization_delete_all(
    database: &crate::Database,
) -> Result<u64, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let statements = [
                ("k_sol_pair_analytic_signals", "DELETE FROM k_sol_pair_analytic_signals"),
                ("k_sol_pair_candles", "DELETE FROM k_sol_pair_candles"),
                ("k_sol_pair_metrics", "DELETE FROM k_sol_pair_metrics"),
                ("k_sol_orderbook_events", "DELETE FROM k_sol_orderbook_events"),
                ("k_sol_trade_events", "DELETE FROM k_sol_trade_events"),
                (
                    "locally_covered_upstream_instruction_observation_links",
                    r#"
UPDATE k_sol_instruction_observations
SET decoded_event_id = NULL
WHERE decoded_event_id IN (
    SELECT de.id
    FROM k_sol_dex_decoded_events de
    WHERE de.protocol_name = 'upstream_git'
      AND de.event_kind = 'upstream_git.instruction_match'
      AND (
          COALESCE(json_extract(de.payload_json, '$.upstreamDecoderCode'), '')
          || '|'
          || COALESCE(json_extract(de.payload_json, '$.upstreamEntryName'), '')
          || '|'
          || COALESCE(json_extract(de.payload_json, '$.upstreamDiscriminatorHex'), '')
      ) IN (
          SELECT
              ce.decoder_code
              || '|'
              || ce.entry_name
              || '|'
              || ce.discriminator_hex
          FROM k_sol_dex_event_coverage_entries ce
          WHERE ce.local_event_kind IS NOT NULL
            AND ce.local_event_kind <> ''
            AND ce.discriminator_hex IS NOT NULL
            AND ce.discriminator_hex <> ''
      )
)
                    "#,
                ),
                (
                    "locally_covered_upstream_instruction_matches",
                    r#"
DELETE FROM k_sol_dex_decoded_events
WHERE protocol_name = 'upstream_git'
  AND event_kind = 'upstream_git.instruction_match'
  AND (
      COALESCE(json_extract(payload_json, '$.upstreamDecoderCode'), '')
      || '|'
      || COALESCE(json_extract(payload_json, '$.upstreamEntryName'), '')
      || '|'
      || COALESCE(json_extract(payload_json, '$.upstreamDiscriminatorHex'), '')
  ) IN (
      SELECT
          ce.decoder_code
          || '|'
          || ce.entry_name
          || '|'
          || ce.discriminator_hex
      FROM k_sol_dex_event_coverage_entries ce
      WHERE ce.local_event_kind IS NOT NULL
        AND ce.local_event_kind <> ''
        AND ce.discriminator_hex IS NOT NULL
        AND ce.discriminator_hex <> ''
  )
                    "#,
                ),
            ];
            let mut deleted_count = 0_u64;
            for (table_name, statement) in statements {
                let query_result = sqlx::query(statement).execute(pool).await;
                let result = match query_result {
                    Ok(result) => result,
                    Err(error) => {
                        return Err(crate::Error::Db(format!(
                            "cannot clear {} during local market materialization reset on sqlite: {}",
                            table_name, error
                        )));
                    },
                };
                deleted_count += result.rows_affected();
            }
            return Ok(deleted_count);
        },
    }
}

/// Inserts or updates one trade-event row and returns its stable internal id.
pub async fn query_trade_events_upsert(
    database: &crate::Database,
    dto: &crate::TradeEventDto,
) -> Result<i64, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query(
                r#"
INSERT INTO k_sol_trade_events (
    dex_id,
    pool_id,
    pair_id,
    transaction_id,
    decoded_event_id,
    signature,
    slot,
    trade_side,
    base_token_id,
    quote_token_id,
    base_amount_raw,
    quote_amount_raw,
    price_quote_per_base,
    source_kind,
    source_endpoint_name,
    payload_json,
    created_at,
    updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(decoded_event_id) DO UPDATE SET
    dex_id = excluded.dex_id,
    pool_id = excluded.pool_id,
    pair_id = excluded.pair_id,
    transaction_id = excluded.transaction_id,
    signature = excluded.signature,
    slot = COALESCE(excluded.slot, k_sol_trade_events.slot),
    trade_side = excluded.trade_side,
    base_token_id = excluded.base_token_id,
    quote_token_id = excluded.quote_token_id,
    base_amount_raw = COALESCE(excluded.base_amount_raw, k_sol_trade_events.base_amount_raw),
    quote_amount_raw = COALESCE(excluded.quote_amount_raw, k_sol_trade_events.quote_amount_raw),
    price_quote_per_base = COALESCE(excluded.price_quote_per_base, k_sol_trade_events.price_quote_per_base),
    source_kind = excluded.source_kind,
    source_endpoint_name = COALESCE(excluded.source_endpoint_name, k_sol_trade_events.source_endpoint_name),
    payload_json = excluded.payload_json,
    updated_at = excluded.updated_at
                "#,
            )
            .bind(dto.dex_id)
            .bind(dto.pool_id)
            .bind(dto.pair_id)
            .bind(dto.transaction_id)
            .bind(dto.decoded_event_id)
            .bind(dto.signature.clone())
            .bind(dto.slot)
            .bind(trade_side_to_string(dto.trade_side))
            .bind(dto.base_token_id)
            .bind(dto.quote_token_id)
            .bind(dto.base_amount_raw.clone())
            .bind(dto.quote_amount_raw.clone())
            .bind(dto.price_quote_per_base)
            .bind(dto.source_kind.to_i16())
            .bind(dto.source_endpoint_name.clone())
            .bind(dto.payload_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_trade_events on sqlite: {}",
                    error
                )));
            }

            let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
                r#"
SELECT id
FROM k_sol_trade_events
WHERE decoded_event_id = ?
LIMIT 1
                "#,
            )
            .bind(dto.decoded_event_id)
            .fetch_one(pool)
            .await;
            match id_result {
                Ok(id) => return Ok(id),
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot fetch k_sol_trade_events id for decoded_event_id '{}' on sqlite: {}",
                        dto.decoded_event_id, error
                    )));
                },
            }
        },
    }
}

/// Returns one trade-event row identified by its decoded-event id, if it exists.
pub async fn query_trade_events_get_by_decoded_event_id(
    database: &crate::Database,
    decoded_event_id: i64,
) -> Result<std::option::Option<crate::TradeEventDto>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query_as::<sqlx::Sqlite, crate::TradeEventEntity>(
                r#"
SELECT
    id,
    dex_id,
    pool_id,
    pair_id,
    transaction_id,
    decoded_event_id,
    signature,
    slot,
    trade_side,
    base_token_id,
    quote_token_id,
    base_amount_raw,
    quote_amount_raw,
    price_quote_per_base,
    source_kind,
    source_endpoint_name,
    payload_json,
    created_at,
    updated_at
FROM k_sol_trade_events
WHERE decoded_event_id = ?
LIMIT 1
                    "#,
            )
            .bind(decoded_event_id)
            .fetch_optional(pool)
            .await;
            let entity_option = match query_result {
                Ok(entity_option) => entity_option,
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot read k_sol_trade_events by decoded_event_id '{}' on sqlite: {}",
                        decoded_event_id, error
                    )));
                },
            };
            match entity_option {
                Some(entity) => return crate::TradeEventDto::try_from(entity).map(Some),
                None => return Ok(None),
            }
        },
    }
}

/// Lists trade-event rows for one pair id ordered by creation time then id.
pub async fn query_trade_events_list_by_pair_id(
    database: &crate::Database,
    pair_id: i64,
) -> Result<std::vec::Vec<crate::TradeEventDto>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query_as::<sqlx::Sqlite, crate::TradeEventEntity>(
                r#"
SELECT
    id,
    dex_id,
    pool_id,
    pair_id,
    transaction_id,
    decoded_event_id,
    signature,
    slot,
    trade_side,
    base_token_id,
    quote_token_id,
    base_amount_raw,
    quote_amount_raw,
    price_quote_per_base,
    source_kind,
    source_endpoint_name,
    payload_json,
    created_at,
    updated_at
FROM k_sol_trade_events
WHERE pair_id = ?
ORDER BY created_at ASC, id ASC
                    "#,
            )
            .bind(pair_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_trade_events by pair_id '{}' on sqlite: {}",
                        pair_id, error
                    )));
                },
            };
            let mut dtos = std::vec::Vec::new();
            for entity in entities {
                let dto_result = crate::TradeEventDto::try_from(entity);
                let dto = match dto_result {
                    Ok(dto) => dto,
                    Err(error) => return Err(error),
                };
                dtos.push(dto);
            }
            return Ok(dtos);
        },
    }
}

/// Lists trade-event rows for one transaction id ordered by id.
pub async fn query_trade_events_list_by_transaction_id(
    database: &crate::Database,
    transaction_id: i64,
) -> Result<std::vec::Vec<crate::TradeEventDto>, crate::Error> {
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result = sqlx::query_as::<sqlx::Sqlite, crate::TradeEventEntity>(
                r#"
SELECT
    id,
    dex_id,
    pool_id,
    pair_id,
    transaction_id,
    decoded_event_id,
    signature,
    slot,
    trade_side,
    base_token_id,
    quote_token_id,
    base_amount_raw,
    quote_amount_raw,
    price_quote_per_base,
    source_kind,
    source_endpoint_name,
    payload_json,
    created_at,
    updated_at
FROM k_sol_trade_events
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_trade_events by transaction_id '{}' on sqlite: {}",
                        transaction_id, error
                    )));
                },
            };
            let mut dtos = std::vec::Vec::new();
            for entity in entities {
                let dto_result = crate::TradeEventDto::try_from(entity);
                let dto = match dto_result {
                    Ok(dto) => dto,
                    Err(error) => return Err(error),
                };
                dtos.push(dto);
            }
            return Ok(dtos);
        },
    }
}

fn trade_side_to_string(value: crate::SwapTradeSide) -> &'static str {
    match value {
        crate::SwapTradeSide::BuyBase => return "BuyBase",
        crate::SwapTradeSide::SellBase => return "SellBase",
        crate::SwapTradeSide::Unknown => return "Unknown",
    }
}
