use crate::cache::get_hash;
use crate::common::{deserialize_from_json, serialize_to_json, REGIONS_KEY, REGION_IDENT_KEY};
use crate::get_hash_all_values;
use cal_core::Region;
use redis::aio::MultiplexedConnection;
use redis::{AsyncCommands, RedisError, Value};


/// Retrieves all regions stored in Redis.
///
/// # Returns
/// * `Result<Vec<Region>, RedisError>` - List of regions or a Redis error
pub async fn get_regions(con: MultiplexedConnection) -> Result<Vec<Region>, RedisError> {
    let region_str: Vec<String> = get_hash_all_values(con.clone(), REGIONS_KEY)
        .await?
        .into_values()
        .collect();

    // Deserialize each region
    let mut regions = Vec::with_capacity(region_str.len());
    for str in region_str {
        regions.push(deserialize_from_json(&str)?);
    }

    Ok(regions)
}

/// Retrieves a region by its identifier (could be an ID, name, etc.).
///
/// # Arguments
/// * `con` - Redis connection
/// * `key` - Region identifier to look up
///
/// # Returns
/// * `Result<Option<Region>, RedisError>` - Region if found, None if not found, or a Redis error
pub async fn get_region_by_ident(
    con: MultiplexedConnection,
    key: &str,
) -> Result<Option<Region>, RedisError> {
    // Look up the region ID from the identifiers hash
    match get_hash(con.clone(), "cal:region:idents", key).await? {
        Some(region_id) => get_region_by_id(con, &region_id).await,
        None => Ok(None), // No region found with this identifier
    }
}

/// Retrieves a region by its unique ID.
///
/// # Arguments
/// * `con` - Redis connection
/// * `key` - Region ID
///
/// # Returns
/// * `Result<Option<Region>, RedisError>` - Region if found, None if not found, or a Redis error
pub async fn get_region_by_id(
    con: MultiplexedConnection,
    key: &str,
) -> Result<Option<Region>, RedisError> {
    // Get the region data from Redis
    match get_hash(con, REGIONS_KEY, key).await? {
        Some(value) => Ok(Some(deserialize_from_json(&value)?)),
        None => Ok(None),
    }
}

/// Inserts a region into Redis.
///
/// # Arguments
/// * `con` - Redis connection
/// * `region` - Region object to insert
///
/// # Returns
/// * `Result<(), RedisError>` - Success or a Redis error
pub async fn insert_region(
    mut con: MultiplexedConnection,
    region: Region,
) -> Result<(), RedisError> {
    let value = serialize_to_json(&region)?;

    // Set the region in Redis with explicit type annotation
    let _: String = con
        .hset(REGIONS_KEY, region.id.to_string(), value)
        .await?;

    // Build the idents
    build_region_idents(&mut con, &region).await?;
    
    Ok(())
}

/// Inserts region identifiers into Redis for lookup purposes.
///
/// # Arguments
/// * `con` - Redis connection
/// * `items` - List of (identifier, region_id) pairs to insert
///
/// # Returns
/// * `Result<Value, RedisError>` - Redis operation result or an error
pub async fn insert_region_idents(
    mut con: MultiplexedConnection,
    items: &[(String, String)],
) -> Result<Value, RedisError> {
    let result: Value = con.hset_multiple(REGION_IDENT_KEY, items).await?;
    Ok(result)
}


/// Builds Redis identity mappings for a region
async fn build_region_idents(
    con: &mut MultiplexedConnection,
    region: &Region,
) -> Result<(), RedisError> {

    // Add account ID and domain mappings
    let mut ident_entries = vec![
        (region.id.to_string(), region.id.to_string())
    ];

    // Add DDI name mappings
    for vs in &region.voice_servers {
        ident_entries.push((vs.id.clone(), region.id.to_string()));
        ident_entries.push((vs.public_ip.clone(), region.id.to_string()));
        ident_entries.push((vs.private_ip.clone(), region.id.to_string()));
    }

    // Insert all identity entries
    let _: Value = con.hset_multiple(REGION_IDENT_KEY, &ident_entries).await?;

    Ok(())
}