//! Typed fetch helpers for Kamino-specific Redis data.

use std::collections::HashMap;

use solana_pubkey::Pubkey;

use pyra_types::{Cache, KaminoObligation, KaminoReserve};

use crate::{RedisClient, RedisError, RedisKey, RedisResult};

impl RedisClient {
    /// Fetch a Kamino obligation by the vault (authority) address and lending market.
    pub async fn fetch_kamino_obligation(
        &self,
        vault_address: &Pubkey,
        lending_market: &Pubkey,
    ) -> RedisResult<Option<KaminoObligation>> {
        let key = RedisKey::kamino_obligation(vault_address, lending_market);
        let cached: Option<Cache<KaminoObligation>> = self.get_json(key.as_str()).await?;
        Ok(cached.map(|c| c.account))
    }

    /// Reverse lookup: Kamino Obligation account pubkey → vault authority (owner).
    pub async fn fetch_kamino_obligation_by_pubkey(
        &self,
        obligation_pubkey: &Pubkey,
    ) -> RedisResult<Pubkey> {
        let key = RedisKey::kamino_obligation_by_pubkey(obligation_pubkey);
        let raw = self
            .get(key.as_str())
            .await?
            .ok_or_else(|| RedisError::NotFound("Kamino obligation reverse mapping".into()))?;
        raw.parse::<Pubkey>()
            .map_err(|_| RedisError::NotFound("Invalid pubkey in obligation reverse mapping".into()))
    }

    /// Fetch a single Kamino reserve by its pubkey.
    pub async fn fetch_kamino_reserve(
        &self,
        reserve_pubkey: &Pubkey,
    ) -> RedisResult<Option<KaminoReserve>> {
        let key = RedisKey::kamino_reserve(reserve_pubkey);
        let cached: Option<Cache<KaminoReserve>> = self.get_json(key.as_str()).await?;
        Ok(cached.map(|c| c.account))
    }

    /// Fetch multiple Kamino reserves by pubkey in a single MGET round-trip.
    ///
    /// Returns only the reserves that were found and successfully deserialized.
    pub async fn fetch_kamino_reserves(
        &self,
        pubkeys: &[Pubkey],
    ) -> RedisResult<HashMap<Pubkey, KaminoReserve>> {
        if pubkeys.is_empty() {
            return Ok(HashMap::new());
        }

        let keys: Vec<String> = pubkeys
            .iter()
            .map(|pk| RedisKey::kamino_reserve(pk).to_string())
            .collect();

        let values = self.mget(&keys).await?;
        let mut result = HashMap::with_capacity(pubkeys.len());

        for (pk, val) in pubkeys.iter().zip(values.into_iter()) {
            if let Some(raw) = val {
                if let Ok(cached) = serde_json::from_str::<Cache<KaminoReserve>>(&raw) {
                    result.insert(*pk, cached.account);
                }
            }
        }

        Ok(result)
    }

    /// Fetch all Kamino reserves by scanning `account:kamino:reserve:*` keys.
    ///
    /// Uses SCAN + MGET for efficiency. Returns reserve_pubkey -> KaminoReserve.
    pub async fn fetch_all_kamino_reserves(&self) -> RedisResult<HashMap<Pubkey, KaminoReserve>> {
        let pattern = RedisKey::kamino_reserve_glob();
        let keys = self.scan_keys(&pattern).await?;

        if keys.is_empty() {
            return Ok(HashMap::new());
        }

        let prefix_with_colon = format!("{}:", RedisKey::KAMINO_RESERVE_PREFIX);
        let values = self.mget(&keys).await?;
        let mut result = HashMap::with_capacity(keys.len());

        for (key, val) in keys.iter().zip(values.into_iter()) {
            if let Some(raw) = val {
                if let Ok(cached) = serde_json::from_str::<Cache<KaminoReserve>>(&raw) {
                    if let Some(pk_str) = key.strip_prefix(prefix_with_colon.as_str()) {
                        if let Ok(pk) = pk_str.parse::<Pubkey>() {
                            result.insert(pk, cached.account);
                        }
                    }
                }
            }
        }

        Ok(result)
    }
}
