//! Organization service layer providing business logic for organization management operations.
//!
//! Implements the service layer of the organization management system, handling business logic,
//! validation, and orchestration.
//!
//! For full documentation, architecture, and examples, see the [crate README](../README.md).

use forge_models::{MemberSummary, Org};
use forge_repositories::OrgRepository;

/// Service layer implementation for organization business logic and operations.
///
/// Orchestrates organization management operations, handling business logic, validation,
/// and data transformation. See [crate README](../README.md) for details.
///
/// # Thread Safety
/// Thread-safe when used with a thread-safe repository.
pub struct OrgService<R: OrgRepository> {
    /// The repository instance used for all data persistence operations.
    ///
    /// This repository handles the actual storage and retrieval of organization
    /// and member data, abstracting away the specific database implementation details.
    repository: R,
}

impl<R: OrgRepository> OrgService<R> {
    /// Creates a new organization service with the provided repository.
    ///
    /// The repository must be initialized and ready for use.
    pub fn new(repository: R) -> Self {
        Self { repository }
    }

    /// Creates a new organization with the provided ID and JSON data.
    ///
    /// Handles validation, duplicate checking, and persistence.
    /// See [crate README](../README.md) for JSON format and business rules.
    pub fn create_org(&self, key: String, json_data: String) -> Result<Org, String> {
        // Parse JSON data to Org struct
        let mut org: Org =
            serde_json::from_str(&json_data).map_err(|e| format!("Invalid Org JSON: {}", e))?;

        // Override ID with the provided parameter (ensures consistency)
        org.id = key;

        // Validate organization name is not empty
        if org.name.trim().is_empty() {
            return Err("Organization name cannot be empty".to_string());
        }

        // Check if organization already exists to prevent duplicates
        if self.repository.exists(&org.id)? {
            return Err(format!("Organization with ID '{}' already exists", org.id));
        }

        // Store the organization in the repository
        self.repository.create(&org)?;

        Ok(org)
    }

    /// Retrieves an organization by its unique identifier with automatic fallback to default.
    ///
    /// Implements a fallback pattern: if the organization doesn't exist, the
    /// organization with ID "default" is retrieved from the repository.
    pub fn get_org(&self, key: String) -> Result<Org, String> {
        // Attempt to retrieve organization from repository
        match self.repository.get_by_id(&key)? {
            // Organization found - return it
            Some(org) => Ok(org),
            // Organization not found - retrieve the default organization instead
            None => self
                .repository
                .get_by_id("default")?
                .ok_or_else(|| "Default organization not found".to_string()),
        }
    }

    /// Updates an existing organization with new data from JSON.
    ///
    /// Handles partial updates, validation, and persistence.
    /// See [crate README](../README.md) for JSON format and concurrency details.
    pub fn update_org(&self, key: String, json_update: String) -> Result<Org, String> {
        // Retrieve existing organization from repository
        let mut org = match self.repository.get_by_id(&key)? {
            Some(org) => org,
            None => return Err(format!("Organization with ID '{}' not found", key)),
        };

        // Parse and validate JSON update data
        let update_data: serde_json::Value =
            serde_json::from_str(&json_update).map_err(|e| format!("Invalid JSON: {}", e))?;

        // Ensure update data is a JSON object
        if !update_data.is_object() {
            return Err("Update data must be a JSON object".to_string());
        }

        // Create a temporary copy to safely apply updates with validation
        let mut updated_org = org.clone();

        // Apply updates field by field
        if let Some(obj) = update_data.as_object() {
            for (field, value) in obj {
                match field.as_str() {
                    "id" => {
                        if let Some(id_str) = value.as_str() {
                            updated_org.id = id_str.to_string();
                        } else {
                            return Err("ID must be a string".to_string());
                        }
                    }
                    "owner" => {
                        if let Some(owner_str) = value.as_str() {
                            updated_org.owner = owner_str.to_string();
                        } else {
                            return Err("Owner must be a string".to_string());
                        }
                    }
                    "name" => {
                        if let Some(name_str) = value.as_str() {
                            updated_org.name = name_str.to_string();
                        } else {
                            return Err("Name must be a string".to_string());
                        }
                    }
                    "funds" => {
                        if let Some(funds_val) = value.as_f64() {
                            updated_org.funds = funds_val;
                        } else {
                            return Err("Funds must be a number".to_string());
                        }
                    }
                    "reputation" => {
                        if let Some(rep_val) = value.as_i64() {
                            updated_org.reputation = rep_val;
                        } else {
                            return Err("Reputation must be an integer".to_string());
                        }
                    }
                    _ => {
                        return Err(format!("Unknown field: {}", field));
                    }
                }
            }
        }

        // Validate the updated organization before committing changes
        updated_org
            .validate()
            .map_err(|e| format!("Validation failed: {}", e))?;

        // Only commit changes after validation passes
        org = updated_org;

        // Persist the updated organization to repository
        self.repository.update(&org)?;

        Ok(org)
    }

    /// Permanently deletes an organization from the system.
    ///
    /// Irreversible operation. Delegates to repository.
    pub fn delete_org(&self, key: String) -> Result<(), String> {
        let redis_key = format!("org:{}", key);
        let assets_key = format!("org:{}:assets", key);
        let members_key = format!("org:{}:members", key);

        // Delegate deletion to repository layer
        self.repository.delete(&redis_key)?;
        self.repository.delete(&assets_key)?;
        self.repository.delete(&members_key)?;

        Ok(())
    }

    /// Checks if an organization exists in the system.
    ///
    /// Lightweight check without data retrieval.
    pub fn org_exists(&self, key: String) -> Result<bool, String> {
        // Delegate existence check to repository layer
        self.repository.exists(&key)
    }

    /// Adds a new member UID to an organization with validation.
    pub fn add_member(&self, key: String, member_uid: String) -> Result<(), String> {
        // Verify organization exists before adding member
        if !self.repository.exists(&key)? {
            return Err(format!("Organization with ID '{}' not found", key));
        }

        // Add member UID to organization through repository
        self.repository.add_member(&key, &member_uid)
    }

    /// Retrieves all members of an organization as a UID to name mapping.
    pub fn get_members(&self, key: String) -> Result<Vec<MemberSummary>, String> {
        // Delegate member retrieval to repository layer
        self.repository.get_members(&key)
    }

    /// Permanently removes a specific member from an organization.
    ///
    /// Irreversible operation. Delegates to repository.
    pub fn remove_member(&self, key: String, member_uid: String) -> Result<(), String> {
        // Verify organization exists before attempting member removal
        if !self.repository.exists(&key)? {
            return Err(format!("Organization with ID '{}' not found", key));
        }

        // Delegate member removal to repository layer
        self.repository.remove_member(&key, &member_uid)
    }
}
