//! Actor repository implementation for data persistence operations.
//!
//! This module provides the data access layer for actor (player) management,
//! implementing the repository pattern to abstract database operations.
//!
//! For full documentation and examples, see the [crate README](../README.md).

use forge_models::Actor;
use forge_shared::{RedisClient, parse_json_value, parse_redis_value};

/// Repository trait defining the contract for actor data operations.
///
/// This trait abstracts the data persistence layer, allowing different
/// implementations (Redis, PostgreSQL, etc.) while maintaining a consistent
/// interface for the service layer. All implementations must be thread-safe.
pub trait ActorRepository: Send + Sync {
    /// Creates a new actor in the repository.
    fn create(&self, actor: &Actor) -> Result<(), String>;

    /// Retrieves an actor by their unique identifier.
    fn get_by_id(&self, id: &str) -> Result<Option<Actor>, String>;

    /// Updates an existing actor with new data.
    fn update(&self, actor: &Actor) -> Result<(), String>;

    /// Permanently removes an actor from the repository.
    fn delete(&self, id: &str) -> Result<(), String>;

    /// Checks if an actor exists in the repository.
    fn exists(&self, id: &str) -> Result<bool, String>;
}

/// Redis-based implementation of the ActorRepository trait.
///
/// This implementation uses Redis hash maps to store actor data, providing
/// efficient field-level access and atomic operations. Each actor is stored
/// as a separate hash with the key format `actor:{uid}`.
pub struct RedisActorRepository<C: RedisClient> {
    /// The Redis client used for all database operations.
    ///
    /// This client handles the actual communication with Redis, including
    /// connection management, command execution, and error handling.
    client: C,
}

impl<C: RedisClient> RedisActorRepository<C> {
    /// Creates a new Redis actor repository with the provided client.
    pub fn new(client: C) -> Self {
        Self { client }
    }
}

impl<C: RedisClient> ActorRepository for RedisActorRepository<C> {
    /// Creates a new actor in Redis using hash map storage.
    ///
    /// Stores each actor as a Redis hash map with the key format `actor:{uid}`.
    /// Each field of the actor struct becomes a field in the Redis hash.
    fn create(&self, actor: &Actor) -> Result<(), String> {
        // Generate Redis key using actor UID
        let redis_key = format!("actor:{}", actor.uid());

        // Serialize actor to JSON string
        let actor_json = serde_json::to_string(actor)
            .map_err(|e| format!("Failed to serialize actor: {}", e))?;

        // Parse JSON string back to Value for field extraction
        let json_value: serde_json::Value = serde_json::from_str(&actor_json)
            .map_err(|e| format!("Failed to parse actor JSON: {}", e))?;

        // Extract fields from JSON object
        if let serde_json::Value::Object(actor_map) = json_value {
            // Convert each field to Redis-compatible format
            let fields: Vec<(String, String)> = actor_map
                .into_iter()
                .map(|(field, value)| (field, parse_json_value(&value)))
                .collect();

            // Store all fields atomically using Redis HMSET
            self.client.hash_mset(redis_key, fields)
        } else {
            Err("Failed to convert actor to object".to_string())
        }
    }

    /// Retrieves an actor from Redis by their unique identifier.
    ///
    /// Uses Redis HGETALL to retrieve all fields of the actor hash map,
    /// then reconstructs the Actor struct through JSON deserialization.
    fn get_by_id(&self, id: &str) -> Result<Option<Actor>, String> {
        // Generate Redis key using actor UID
        let redis_key = format!("actor:{}", id);

        // Retrieve all hash fields from Redis
        let actor_string = self.client.hash_get_all(redis_key)?;

        // Return None if no data found (actor doesn't exist)
        if actor_string.is_empty() {
            return Ok(None);
        }

        // Parse comma-separated field-value pairs
        let parts: Vec<&str> = actor_string.split(", ").collect();
        let mut json_map = serde_json::Map::new();
        let mut i = 0;

        // Process pairs of field names and values
        while i + 1 < parts.len() {
            let key = parts[i];
            let value = parts[i + 1];

            // Convert Redis string value back to proper JSON type
            let json_value = parse_redis_value(value);
            json_map.insert(key.to_string(), json_value);

            i += 2; // Move to next field-value pair
        }

        // Reconstruct Actor from JSON object
        let json_obj = serde_json::Value::Object(json_map);
        match serde_json::from_value::<Actor>(json_obj) {
            Ok(actor) => Ok(Some(actor)),
            // Return None for any deserialization errors (corrupted data)
            Err(_) => Ok(None),
        }
    }

    /// Updates an existing actor with the provided data.
    ///
    /// Uses `HMSET` to atomically update the provided fields. Fields present in Redis
    /// but missing from the input are preserved.
    fn update(&self, actor: &Actor) -> Result<(), String> {
        // Delegate to create() which handles both creation and updates
        // Redis HMSET naturally supports upsert behavior
        self.create(actor)
    }

    /// Permanently deletes an actor and all associated data from Redis.
    ///
    /// Removes the entire Redis hash containing the actor's data.
    /// This operation is irreversible.
    fn delete(&self, id: &str) -> Result<(), String> {
        // Generate Redis key using actor UID
        let redis_key = format!("actor:{}", id);

        // Delete the entire hash key from Redis
        // This removes all fields and the key itself atomically
        self.client.delete_key(redis_key)
    }

    /// Checks if an actor exists in Redis without retrieving the data.
    ///
    /// Uses Redis EXISTS command for a lightweight check.
    fn exists(&self, id: &str) -> Result<bool, String> {
        // Generate Redis key using actor UID
        let redis_key = format!("actor:{}", id);

        // Check if the key exists in Redis
        // This is a lightweight operation that doesn't retrieve data
        self.client.key_exists(redis_key)
    }
}
