// file: kb_lib/src/pair_symbol.rs

//! Pair display-symbol derivation.
//!
//! This module derives stable pair labels from the token rows already persisted
//! in `k_sol_tokens`. It deliberately keeps SQL access in `db/queries` and only
//! orchestrates DTO reads and updates.

/// Summary produced by a pair-symbol refresh pass.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PairSymbolRefreshResult {
    /// Number of pairs considered by this pass.
    pub total_pair_count: usize,
    /// Number of pair rows updated.
    pub updated_pair_count: usize,
    /// Number of pair rows already correct.
    pub skipped_pair_count: usize,
    /// Number of pairs skipped because a related token row was missing.
    pub missing_token_count: usize,
}

/// Service that refreshes `k_sol_pairs.symbol` from base and quote token metadata.
#[derive(Debug, Clone)]
pub struct PairSymbolRefreshService {
    database: std::sync::Arc<crate::Database>,
}

impl PairSymbolRefreshService {
    /// Creates a new pair-symbol refresh service.
    pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
        return Self { database };
    }

    /// Refreshes all persisted pair display symbols.
    pub async fn refresh_pair_symbols(
        &self,
    ) -> Result<crate::PairSymbolRefreshResult, crate::Error> {
        return refresh_pair_symbols(self.database.as_ref()).await;
    }
}

/// Refreshes all persisted pair display symbols.
pub async fn refresh_pair_symbols(
    database: &crate::Database,
) -> Result<crate::PairSymbolRefreshResult, crate::Error> {
    let pairs_result = crate::query_pairs_list(database).await;
    let pairs = match pairs_result {
        Ok(pairs) => pairs,
        Err(error) => return Err(error),
    };
    let mut result = PairSymbolRefreshResult {
        total_pair_count: pairs.len(),
        ..Default::default()
    };
    for pair in pairs {
        let pair_id = match pair.id {
            Some(pair_id) => pair_id,
            None => {
                return Err(crate::Error::InvalidState("pair row has no internal id".to_string()));
            },
        };
        let base_token_result = crate::query_tokens_get_by_id(database, pair.base_token_id).await;
        let base_token_option = match base_token_result {
            Ok(base_token_option) => base_token_option,
            Err(error) => return Err(error),
        };
        let base_token = match base_token_option {
            Some(base_token) => base_token,
            None => {
                result.missing_token_count += 1;
                tracing::warn!(
                    pair_id = pair_id,
                    base_token_id = pair.base_token_id,
                    "cannot refresh pair symbol because base token is missing"
                );
                continue;
            },
        };
        let quote_token_result = crate::query_tokens_get_by_id(database, pair.quote_token_id).await;
        let quote_token_option = match quote_token_result {
            Ok(quote_token_option) => quote_token_option,
            Err(error) => return Err(error),
        };
        let quote_token = match quote_token_option {
            Some(quote_token) => quote_token,
            None => {
                result.missing_token_count += 1;
                tracing::warn!(
                    pair_id = pair_id,
                    quote_token_id = pair.quote_token_id,
                    "cannot refresh pair symbol because quote token is missing"
                );
                continue;
            },
        };
        let next_symbol = build_pair_symbol(
            base_token.symbol.as_deref(),
            base_token.mint.as_str(),
            quote_token.symbol.as_deref(),
            quote_token.mint.as_str(),
        );
        let current_symbol = normalize_optional_symbol(pair.symbol.as_deref());
        if current_symbol.as_deref() == Some(next_symbol.as_str()) {
            result.skipped_pair_count += 1;
            continue;
        }
        let update_result =
            crate::query_pairs_update_symbol(database, pair_id, Some(next_symbol.as_str())).await;
        match update_result {
            Ok(updated) => {
                if updated {
                    result.updated_pair_count += 1;
                } else {
                    result.skipped_pair_count += 1;
                }
            },
            Err(error) => return Err(error),
        }
    }
    return Ok(result);
}

/// Builds a readable pair symbol from token symbols with mint fallback.
pub fn build_pair_symbol(
    base_symbol: std::option::Option<&str>,
    base_mint: &str,
    quote_symbol: std::option::Option<&str>,
    quote_mint: &str,
) -> std::string::String {
    let base_label = pick_token_label(base_symbol, base_mint);
    let quote_label = pick_token_label(quote_symbol, quote_mint);
    return format!("{}/{}", base_label, quote_label);
}

fn pick_token_label(symbol: std::option::Option<&str>, mint: &str) -> std::string::String {
    let symbol_option = normalize_optional_symbol(symbol);
    match symbol_option {
        Some(symbol) => return symbol,
        None => return mint.trim().to_string(),
    }
}

fn normalize_optional_symbol(
    symbol: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let symbol = match symbol {
        Some(symbol) => symbol.trim(),
        None => return None,
    };
    if symbol.is_empty() {
        return None;
    }
    return Some(symbol.to_string());
}

#[cfg(test)]
mod tests {
    async fn make_database() -> std::sync::Arc<crate::Database> {
        let tempdir_result = tempfile::tempdir();
        let tempdir = match tempdir_result {
            Ok(tempdir) => tempdir,
            Err(error) => panic!("tempdir must succeed: {}", error),
        };
        let database_path = tempdir.path().join("pair_symbol.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,
            },
        };
        let database_result = crate::Database::connect_and_initialize(&config).await;
        let database = match database_result {
            Ok(database) => database,
            Err(error) => panic!("database init must succeed: {}", error),
        };
        return std::sync::Arc::new(database);
    }

    #[test]
    fn build_pair_symbol_uses_symbol_when_available() {
        let wsol_mint = crate::WSOL_MINT_ID.to_string();
        let symbol = crate::build_pair_symbol(
            Some(" BASE "),
            "BaseMint111",
            Some("WSOL"),
            wsol_mint.as_str(),
        );
        assert_eq!(symbol, "BASE/WSOL");
    }

    #[test]
    fn build_pair_symbol_falls_back_to_mint_when_symbol_is_missing() {
        let wsol_mint = crate::WSOL_MINT_ID.to_string();
        let symbol =
            crate::build_pair_symbol(None, "BaseMint111", Some("WSOL"), wsol_mint.as_str());
        assert_eq!(symbol, "BaseMint111/WSOL");
    }

    #[tokio::test]
    async fn refresh_pair_symbols_updates_empty_symbol() {
        let database = make_database().await;
        let dex_id_result = crate::query_dexs_upsert(
            database.as_ref(),
            &crate::DexDto::new("test_dex".to_string(), "Test DEX".to_string(), None, None, true),
        )
        .await;
        let dex_id = match dex_id_result {
            Ok(dex_id) => dex_id,
            Err(error) => panic!("dex upsert must succeed: {}", error),
        };
        let base_token_id_result = crate::query_tokens_upsert(
            database.as_ref(),
            &crate::TokenDto::new(
                "BaseMint111".to_string(),
                Some("BASE".to_string()),
                Some("Base Token".to_string()),
                Some(6),
                crate::SPL_TOKEN_PROGRAM_ID.to_string(),
                false,
            ),
        )
        .await;
        let base_token_id = match base_token_id_result {
            Ok(base_token_id) => base_token_id,
            Err(error) => panic!("base token upsert must succeed: {}", error),
        };
        let quote_token_id_result = crate::query_tokens_upsert(
            database.as_ref(),
            &crate::TokenDto::new(
                crate::WSOL_MINT_ID.to_string(),
                Some("WSOL".to_string()),
                Some("Wrapped SOL".to_string()),
                Some(9),
                crate::SPL_TOKEN_PROGRAM_ID.to_string(),
                true,
            ),
        )
        .await;
        let quote_token_id = match quote_token_id_result {
            Ok(quote_token_id) => quote_token_id,
            Err(error) => panic!("quote token upsert must succeed: {}", error),
        };
        let pool_id_result = crate::query_pools_upsert(
            database.as_ref(),
            &crate::PoolDto::new(
                dex_id,
                "Pool111".to_string(),
                crate::PoolKind::Amm,
                crate::PoolStatus::Active,
            ),
        )
        .await;
        let pool_id = match pool_id_result {
            Ok(pool_id) => pool_id,
            Err(error) => panic!("pool upsert must succeed: {}", error),
        };
        let pair_id_result = crate::query_pairs_upsert(
            database.as_ref(),
            &crate::PairDto::new(dex_id, pool_id, base_token_id, quote_token_id, None),
        )
        .await;
        let pair_id = match pair_id_result {
            Ok(pair_id) => pair_id,
            Err(error) => panic!("pair upsert must succeed: {}", error),
        };
        let result = crate::refresh_pair_symbols(database.as_ref()).await;
        let result = match result {
            Ok(result) => result,
            Err(error) => panic!("pair symbol refresh must succeed: {}", error),
        };
        assert_eq!(result.total_pair_count, 1);
        assert_eq!(result.updated_pair_count, 1);
        let pair_result = crate::query_pairs_get_by_pool_id(database.as_ref(), pool_id).await;
        let pair_option = match pair_result {
            Ok(pair_option) => pair_option,
            Err(error) => panic!("pair fetch must succeed: {}", error),
        };
        let pair = match pair_option {
            Some(pair) => pair,
            None => panic!("pair must exist"),
        };
        assert_eq!(pair.id, Some(pair_id));
        assert_eq!(pair.symbol.as_deref(), Some("BASE/WSOL"));
    }
}
