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

use forge_models::Bank;
use forge_repositories::BankRepository;

/// Service layer implementation for bank business logic and operations.
///
/// Orchestrates bank 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 BankService<R: BankRepository> {
    /// The repository instance used for all data persistence operations.
    ///
    /// This repository handles the actual storage and retrieval of bank data,
    /// abstracting away the specific database implementation details.
    repository: R,
}

impl<R: BankRepository> BankService<R> {
    /// Creates a new bank 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 bank 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(&self, key: String, json_data: String) -> Result<Bank, String> {
        // Parse JSON data to Bank struct
        let mut bank: Bank =
            serde_json::from_str(&json_data).map_err(|e| format!("Invalid Bank JSON: {}", e))?;

        // Set UID from parameter (authoritative source)
        bank.uid = key;

        // Check if bank already exists to prevent duplicates
        if self.repository.exists(&bank.uid)? {
            return Err(format!("Bank with uid '{}' already exists", bank.uid));
        }

        if let Err(e) = bank.validate() {
            return Err(format!("Invalid Bank JSON: {}", e));
        }

        self.repository.create(&bank)?;

        Ok(bank)
    }

    /// Retrieves an bank by their unique identifier with automatic fallback creation.
    ///
    /// Implements a "get-or-create" pattern: if the bank doesn't exist, a new one
    /// with default values is returned (but not persisted).
    pub fn get_bank(&self, key: String) -> Result<Bank, String> {
        // Attempt to retrieve bank from repository
        match self.repository.get_by_id(&key)? {
            // Bank found - return it
            Some(bank) => Ok(bank),
            // Bank not found - create fallback bank with default values
            None => Err(format!("Bank with UID '{}' not found", key)),
        }
    }

    /// Updates an existing bank 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_bank(&self, key: String, json_update: String) -> Result<Bank, String> {
        // Retrieve existing bank from repository
        let mut bank = match self.repository.get_by_id(&key)? {
            Some(bank) => bank,
            None => return Err(format!("Bank with UID '{}' 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_bank = bank.clone();

        // Apply updates field by field
        if let Some(obj) = update_data.as_object() {
            for (field, value) in obj {
                match field.as_str() {
                    "uid" => {
                        // Skip UID - it's immutable and set by the system
                        continue;
                    }
                    "name" => {
                        if let Some(name) = value.as_str() {
                            updated_bank.name = name.to_string();
                        } else {
                            return Err("Name must be a string".to_string());
                        }
                    }
                    "bank" => {
                        if let Some(bank_val) = value.as_f64() {
                            updated_bank.bank = bank_val;
                        } else {
                            return Err("Bank balance must be a number".to_string());
                        }
                    }
                    "cash" => {
                        if let Some(cash_val) = value.as_f64() {
                            updated_bank.cash = cash_val;
                        } else {
                            return Err("Cash must be a number".to_string());
                        }
                    }
                    "earnings" => {
                        if let Some(earnings_val) = value.as_f64() {
                            updated_bank.earnings = earnings_val;
                        } else {
                            return Err("Earnings must be a number".to_string());
                        }
                    }
                    "pin" => {
                        if let Some(pin_val) = value.as_u64() {
                            updated_bank.pin = pin_val;
                        } else {
                            return Err("PIN must be a number".to_string());
                        }
                    }
                    "transactions" => {
                        if let Some(arr) = value.as_array() {
                            updated_bank.transactions = arr
                                .iter()
                                .filter_map(|v| v.as_str().map(|s| s.to_string()))
                                .collect();
                        } else {
                            return Err("Transactions must be an array".to_string());
                        }
                    }
                    _ => {
                        return Err(format!("Unknown field: {}", field));
                    }
                }
            }
        }

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

        // Only commit changes after validation passes
        bank = updated_bank;

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

        Ok(bank)
    }

    /// Permanently deletes an bank from the system.
    ///
    /// Irreversible operation. Delegates to repository.
    pub fn delete_bank(&self, key: String) -> Result<(), String> {
        // Delegate deletion to repository layer
        // Future enhancements could add business logic here:
        // - Authorization checks
        // - Audit logging
        // - Cascade deletion
        // - Soft deletion
        self.repository.delete(&key)
    }

    /// Checks if an bank exists in the system.
    ///
    /// Lightweight check without data retrieval.
    pub fn bank_exists(&self, key: String) -> Result<bool, String> {
        // Delegate existence check to repository layer
        // This is a lightweight operation that doesn't retrieve data
        self.repository.exists(&key)
    }
}
