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

use forge_models::{MemberSummary, Org};
use forge_shared::{RedisClient, parse_json_value, parse_redis_value};

/// Repository trait defining the contract for organization 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 OrgRepository: Send + Sync {
    /// Creates a new organization in the repository.
    fn create(&self, org: &Org) -> Result<(), String>;

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

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

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

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

    /// Adds a new member UID to an organization.
    fn add_member(&self, org_id: &str, member_uid: &str) -> Result<(), String>;

    /// Retrieves all members of an organization as a list of MemberSummary objects.
    fn get_members(&self, org_id: &str) -> Result<Vec<MemberSummary>, String>;

    /// Removes a specific member from an organization.
    fn remove_member(&self, org_id: &str, member_uid: &str) -> Result<(), String>;
}

/// Redis-based implementation of the OrgRepository trait.
///
/// Uses Redis hash maps for organization data providing
/// efficient field-level access and atomic operations. Each organization is stored
/// as a seperate hash with the key format `org:{org_id}`.
/// Member lists are stored as sets with the key format `org:{org_id}:members`.
pub struct RedisOrgRepository<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 for
    /// both organization and member data operations.
    client: C,
}

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

impl<C: RedisClient> OrgRepository for RedisOrgRepository<C> {
    /// Creates a new organization in Redis using hash map storage.
    ///
    /// Stores each organization as a Redis hash map with the key format `{org_id}:org`.
    /// Each field of the organization struct becomes a field in the Redis hash.
    fn create(&self, org: &Org) -> Result<(), String> {
        // Generate Redis key using organization ID
        let redis_key = format!("org:{}", org.id());

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

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

        // Extract fields from JSON object
        if let serde_json::Value::Object(org_map) = json_value {
            // Convert each field to Redis-compatible format
            let fields: Vec<(String, String)> = org_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 org to object".to_string())
        }
    }

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

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

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

        // Parse comma-separated field-value pairs
        let parts: Vec<&str> = org_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 Org from JSON object
        let json_obj = serde_json::Value::Object(json_map);
        match serde_json::from_value::<Org>(json_obj) {
            Ok(org) => Ok(Some(org)),
            // Return None for any deserialization errors (corrupted data)
            Err(_) => Ok(None),
        }
    }

    /// Updates an existing organization 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, org: &Org) -> Result<(), String> {
        // Delegate to create() which handles both creation and updates
        // Redis HMSET naturally supports upsert behavior
        self.create(org)
    }

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

        // Delete the organization hash key from Redis
        // Note: This does NOT delete member data stored separately
        self.client.delete_key(redis_key)
    }

    /// Checks if an organization 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 organization ID
        let redis_key = format!("org:{}", id);

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

    /// Adds a new member to the organization.
    ///
    /// Stores member data in a Redis list associated with the organization.
    /// Validates that the organization exists before adding the member.
    fn add_member(&self, org_id: &str, member_uid: &str) -> Result<(), String> {
        // Check if organization exists
        if !self.exists(org_id)? {
            return Err(format!("Organization {} does not exist", org_id));
        }

        // Generate Redis key for organization member set
        let redis_key = format!("org:{}:members", org_id);

        // Add member UID to set using SADD
        self.client.set_add(redis_key, member_uid.to_string())
    }

    /// Retrieves all members of the organization.
    ///
    /// Uses Redis SMEMBERS to get all member UIDs, then retrieves member details.
    /// Returns a list of `MemberSummary` objects.
    fn get_members(&self, org_id: &str) -> Result<Vec<MemberSummary>, String> {
        // Generate Redis key for organization member set
        let redis_key = format!("org:{}:members", org_id);

        // Retrieve all member UIDs from the set; fall back to empty on error
        let uids: Vec<String> = match self.client.set_members(redis_key) {
            Ok(v) => v,
            Err(_) => Vec::new(),
        };

        // Pre-allocate result vector
        let mut result: Vec<MemberSummary> = Vec::with_capacity(uids.len());

        for uid in uids {
            if uid.trim().is_empty() {
                continue;
            }

            // Lookup actor name by UID; fall back to "Unknown" on error/missing
            let actor_key = format!("actor:{}", uid);
            let raw_name = match self.client.hash_get(actor_key, "name".to_string()) {
                Ok(n) => n,
                _ => String::new(),
            };

            let name = match parse_redis_value(&raw_name) {
                serde_json::Value::String(s) => s,
                serde_json::Value::Number(n) => n.to_string(),
                serde_json::Value::Bool(b) => b.to_string(),
                _ => "Unknown".to_string(),
            };

            let name = if name.trim().is_empty() {
                "Unknown".to_string()
            } else {
                name
            };

            result.push(MemberSummary { uid, name });
        }

        Ok(result)
    }

    /// Removes a specific member UID from an organization.
    ///
    /// Uses Redis SREM to remove the UID from the organization's member set.
    fn remove_member(&self, org_id: &str, member_uid: &str) -> Result<(), String> {
        // Generate Redis key for organization member set
        let redis_key = format!("org:{}:members", org_id);

        // Remove the UID from the set using SREM
        self.client.set_del(redis_key, member_uid.to_string())
    }
}
