//! # Fiqh Compliance: Business Assets (Urud al-Tijarah)
//!
//! ## Obligation
//! - Based on the Hadith of Samurah bin Jundub: "The Prophet (ﷺ) commanded us to pay Zakat from what we prepared for sale." (Sunan Abu Dawud 1562).
//!
//! ## Valuation Logic
//! - **Formula**: `(Cash + Market Value of Inventory + Good Receivables) - Immediate Liabilities`.
//! - **Valuation**: Inventory must be valued at current *Market Price* at the time of Zakat, not Cost Price.
//! - **Debts**: Deducting `liabilities_due_now` aligns with the principle of *Dayn al-Hal* (immediate debt) preventing Zakat, as supported by AAOIFI Standard 35.

use rust_decimal::Decimal;
use crate::types::{ZakatDetails, ZakatError, ErrorDetails};
use crate::math::ZakatDecimal;
use serde::{Serialize, Deserialize};
use crate::traits::{CalculateZakat, ZakatConfigArgument};
use crate::validation::Validator;

use crate::inputs::IntoZakatDecimal;
use crate::maal::calculator::{calculate_monetary_asset, MonetaryCalcParams};

// Use the zakat_asset! macro to generate common fields and setters
// Use the zakat_ffi_export! macro to generate common fields, setters, and FFI bindings
crate::zakat_ffi_export! {
    /// Represents Business Assets for Zakat Calculation.
    /// 
    /// # Fiqh Note on Liabilities
    /// `liabilities_due_now` represents **Dayn al-Hal** (Immediate Debt). 
    /// Long-term non-commercial debts (like 20-year mortgages) should not be fully deducted; 
    /// only the upcoming year's payments should be considered (per AAOIFI).
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct BusinessZakat {
        // Assets
        pub cash_on_hand: Decimal,
        pub inventory_value: Decimal,
        #[deprecated(since = "1.3.0", note = "Use `receivables_list` instead")]
        pub receivables: Decimal,
        pub receivables_list: Vec<crate::debt::ReceivableItem>,
        // Business-specific liabilities are now unified with `liabilities_due_now`
    }
}

#[allow(deprecated)] // Uses deprecated `liabilities_due_now` and `receivables` for backward compat
impl Default for BusinessZakat {
    fn default() -> Self {
        let (liabilities_due_now, named_liabilities, hawl_satisfied, label, id, _input_errors, acquisition_date) = Self::default_common();
        Self {
            cash_on_hand: Decimal::ZERO,
            inventory_value: Decimal::ZERO,
            receivables: Decimal::ZERO,
            receivables_list: Vec::new(),
            liabilities_due_now,
            named_liabilities,
            hawl_satisfied,
            label,
            id,
            acquisition_date,
            _input_errors,
        }
    }
}

impl BusinessZakat {
    // Note: new(), debt(), hawl(), label() are generated by the macro

    /// Creates a BusinessZakat instance for a business with only cash assets.
    /// Sets defaults: No inventory, no receivables, no liabilities, Hawl satisfied.
    pub fn cash_only(amount: impl IntoZakatDecimal) -> Self {
        Self::new()
            .cash(amount)
            .hawl(true)
    }

    /// Creates a BusinessZakat instance with basic assets (Cash + Inventory).
    /// This is common for small traders.
    pub fn simple(cash: impl IntoZakatDecimal, inventory: impl IntoZakatDecimal) -> Self {
        Self::new()
            .cash(cash)
            .inventory(inventory)
            .hawl(true)
    }

    /// Sets cash on hand.
    /// 
    /// If the value cannot be converted to a valid decimal, the error is
    /// collected and will be returned by `validate()` or `calculate_zakat()`.
    pub fn cash(mut self, cash: impl IntoZakatDecimal) -> Self {
        match cash.into_zakat_decimal() {
            Ok(v) => self.cash_on_hand = v,
            Err(e) => self._input_errors.push(e),
        }
        self
    }

    /// Sets inventory value.
    /// 
    /// If the value cannot be converted to a valid decimal, the error is
    /// collected and will be returned by `validate()` or `calculate_zakat()`.
    pub fn inventory(mut self, inventory: impl IntoZakatDecimal) -> Self {
        match inventory.into_zakat_decimal() {
            Ok(v) => self.inventory_value = v,
            Err(e) => self._input_errors.push(e),
        }
        self
    }

    /// Sets receivables (money owed to the business).
    /// 
    /// If the value cannot be converted to a valid decimal, the error is
    /// collected and will be returned by `validate()` or `calculate_zakat()`.
    #[deprecated(since = "1.3.0", note = "Use `add_receivable` instead for detailed tracking")]
    #[allow(deprecated)]
    pub fn receivables(mut self, receivables: impl IntoZakatDecimal) -> Self {
        match receivables.into_zakat_decimal() {
            Ok(v) => self.receivables = v,
            Err(e) => self._input_errors.push(e),
        }
        self
    }

    /// Adds a receivable with specific quality.
    pub fn add_receivable(mut self, description: impl Into<String>, amount: impl IntoZakatDecimal, quality: crate::debt::ReceivableQuality) -> Self {
        match amount.into_zakat_decimal() {
            Ok(v) => {
                 self.receivables_list.push(crate::debt::ReceivableItem {
                    description: description.into(),
                    amount: v,
                    quality,
                 });
            },
             Err(e) => self._input_errors.push(e),
        }
        self
    }
    /// Sets short-term business liabilities (deducted from gross assets).
    /// 
    /// If the value cannot be converted to a valid decimal, the error is
    /// collected and will be returned by `validate()` or `calculate_zakat()`.
    #[deprecated(since = "1.1.0", note = "Use `add_liability()` for granular liability tracking")]
    #[allow(deprecated)] // Internal usage of deprecated `liabilities_due_now`
    pub fn liabilities(mut self, liabilities: impl IntoZakatDecimal) -> Self {
        match liabilities.into_zakat_decimal() {
            Ok(v) => self.liabilities_due_now = v,
            Err(e) => self._input_errors.push(e),
        }
        self
    }
}

impl CalculateZakat for BusinessZakat {
    fn validate_input(&self) -> Result<(), ZakatError> {
        self.validate()
    }

    #[allow(deprecated)]
    fn calculate_zakat<C: ZakatConfigArgument>(&self, config: C) -> Result<ZakatDetails, ZakatError> {
        // Validate deferred input errors first
        self.validate()?;
        
        let config_cow = config.resolve_config();
        let config = config_cow.as_ref();

        // Validation moved here
        Validator::ensure_non_negative(&[
            ("business_assets", self.cash_on_hand),
            ("business_assets", self.inventory_value),
            // ("business_assets", self.receivables), // Validated implicitly if non-zero, but let's check legacy
             ("liabilities", self.liabilities_due_now),
        ], self.label.clone())?;
        
        if self.receivables < Decimal::ZERO {
             return Err(ZakatError::InvalidInput(Box::new(crate::types::InvalidInputDetails {
                field: "receivables".to_string(),
                value: "Negative receivables".to_string(),
                reason_key: "error-negative-receivables".to_string(),
                source_label: self.label.clone(),
                ..Default::default()
            })));
        }

        // For LowerOfTwo or Silver standard, we need silver price too
        let needs_silver = matches!(
            config.cash_nisab_standard,
            crate::madhab::NisabStandard::Silver | crate::madhab::NisabStandard::LowerOfTwo
        );
        
        if config.gold_price_per_gram <= Decimal::ZERO && !needs_silver {
            return Err(ZakatError::ConfigurationError(Box::new(ErrorDetails {
                code: crate::types::ZakatErrorCode::ConfigError,
                reason_key: "error-gold-price-required".to_string(),
                source_label: self.label.clone(),
                suggestion: Some("Run with --gold-price X or set ZAKAT_GOLD_PRICE env var.".to_string()),
                ..Default::default()
            })));
        }
        if needs_silver && config.silver_price_per_gram <= Decimal::ZERO {
            return Err(ZakatError::ConfigurationError(Box::new(ErrorDetails {
                code: crate::types::ZakatErrorCode::ConfigError,
                reason_key: "error-silver-price-required".to_string(),
                source_label: self.label.clone(),
                suggestion: Some("Run with --silver-price X or set ZAKAT_SILVER_PRICE env var.".to_string()),
                ..Default::default()
            })));
        }
        
        // Dynamic Nisab threshold based on config (Gold, Silver, or LowerOfTwo)
        let nisab_threshold_value = config.get_monetary_nisab_threshold();

        // Dynamic Zakat Rate from strategy (default 2.5%)
        let rate = config.strategy.get_rules().trade_goods_rate;
        
        // Calculate Total Receivables (Legacy + Strong Receivables from List)
        // Weak receivables are excluded.
        let mut total_receivables = self.receivables; // Start with legacy field
        let mut weak_receivables = Decimal::ZERO;

        for item in &self.receivables_list {
            match item.quality {
                crate::debt::ReceivableQuality::Strong => {
                    total_receivables += item.amount;
                }
                crate::debt::ReceivableQuality::Weak => {
                    weak_receivables += item.amount;
                }
            }
        }
        
        let gross_assets = ZakatDecimal::new(self.cash_on_hand)
            .with_source(self.label.clone())
            .checked_add(self.inventory_value)?
            .checked_add(total_receivables)?;
        
        let mut trace_steps = vec![
            crate::types::CalculationStep::initial("step-cash-on-hand", "Cash on Hand", self.cash_on_hand)
                .with_reference("Sunan Abu Dawud 1562"),
            crate::types::CalculationStep::add("step-inventory-value", "Inventory Value", self.inventory_value)
                .with_reference("Fiqh al-Zakah (Commercial Goods)"),
        ];

        if !total_receivables.is_zero() {
            trace_steps.push(
               crate::types::CalculationStep::add("step-receivables-strong", "Receivables (Strong)", total_receivables)
            );
        }
        
        if !weak_receivables.is_zero() {
             // Just a note or handled outside? 
             // We can maybe add a step showing it's excluded, but CalculationStep usually sums up.
             // We'll leave it implicitly excluded but maybe add a note later in ZakatDetails if possible.
        }

        trace_steps.push(crate::types::CalculationStep::result("step-gross-assets", "Gross Assets", *gross_assets));

        // Calculate total liabilities (legacy + named)
        let total_liabilities = self.total_liabilities();

        // Override hawl_satisfied if acquisition_date is present
        let hawl_is_satisfied = if let Some(date) = self.acquisition_date {
            let tracker = crate::hawl::HawlTracker::new(chrono::Local::now().date_naive())
                .acquired_on(date);
            tracker.is_satisfied()
        } else {
            self.hawl_satisfied
        };        

        let params = MonetaryCalcParams {
            total_assets: *gross_assets,
            liabilities: total_liabilities,
            nisab_threshold: nisab_threshold_value,
            rate,
            wealth_type: crate::types::WealthType::Business,
            label: self.label.clone(),
            hawl_satisfied: hawl_is_satisfied,
            asset_id: Some(self.id),
            trace_steps,
            warnings: Vec::new(),
            observer: Some(config.observer.clone()),
        };

        let mut result = calculate_monetary_asset(params)?;
        
        if !weak_receivables.is_zero() {
             result.notes.push(format!("Excluded Weak Receivables: {}. Pay Zakat on this amount only upon receipt.", weak_receivables));
        }
        
        Ok(result)
    }

    fn get_label(&self) -> Option<String> {
        self.label.clone()
    }

    fn get_id(&self) -> uuid::Uuid {
        self.id
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::ZakatConfig;
    use rust_decimal_macros::dec;

    #[test]
    fn test_business_zakat() {
        let config = ZakatConfig { gold_price_per_gram: Decimal::from(100), ..Default::default() };
        
        #[allow(deprecated)]
        let business = BusinessZakat::new()
            .cash(5000.0)
            .inventory(5000.0)
            .liabilities(1000.0)
            .hawl(true);

        let result = business.calculate_zakat(&config).unwrap();

        assert!(result.is_payable);
        assert_eq!(result.net_assets, Decimal::from(9000));
        assert_eq!(result.zakat_due, dec!(225.0)); // 225.0
    }

    #[test]
    fn test_business_below_nisab() {
         let config = ZakatConfig { gold_price_per_gram: Decimal::from(100), ..Default::default() };
         let business = BusinessZakat::new()
             .cash(1000.0)
             .inventory(1000.0);
         
         let result = business.calculate_zakat(&config).unwrap();
         
         assert!(!result.is_payable);
    }

    #[test]
    fn test_business_specific_case() {
        let config = ZakatConfig { gold_price_per_gram: Decimal::from(1000000), ..Default::default() };
        
        #[allow(deprecated)]
        let business = BusinessZakat::new()
            .cash(100000000.0)
            .liabilities(20000000.0)
            .hawl(true);
        
        let result = business.calculate_zakat(&config).unwrap();
        
        assert!(!result.is_payable);
        assert_eq!(result.net_assets, Decimal::from(80000000));
    }

    #[test]
    fn test_business_validation() {
        let config = ZakatConfig::default();
        let business = BusinessZakat::new()
            .cash(-100.0);
            
        let result = business.calculate_zakat(&config);
        assert!(matches!(result, Err(ZakatError::InvalidInput { .. })));
    }
    
    // =============================================================================
    // Feature 1: Granular Liability Management Tests
    // =============================================================================
    
    #[test]
    fn test_add_liability_named() {
        let config = ZakatConfig { gold_price_per_gram: Decimal::from(100), ..Default::default() };
        
        // Use the new add_liability API
        let business = BusinessZakat::new()
            .cash(10000.0)
            .inventory(5000.0)
            .add_liability("Credit Card", 500)
            .add_liability("Mortgage Payment", 1500)
            .add_liability("Car Loan", 1000)
            .hawl(true);
        
        // Verify total liabilities
        assert_eq!(business.total_liabilities(), dec!(3000));
        assert_eq!(business.named_liabilities.len(), 3);
        
        let result = business.calculate_zakat(&config).unwrap();
        
        // Net = 15000 - 3000 = 12000
        // Zakat = 12000 * 0.025 = 300
        assert!(result.is_payable);
        assert_eq!(result.net_assets, dec!(12000));
        assert_eq!(result.zakat_due, dec!(300));
    }
    
    #[test]
    fn test_combined_legacy_and_named_liabilities() {
        let config = ZakatConfig { gold_price_per_gram: Decimal::from(100), ..Default::default() };
        
        // Combine legacy debt() with new add_liability()
        #[allow(deprecated)]
        let business = BusinessZakat::new()
            .cash(10000.0)
            .debt(1000.0)  // Legacy API
            .add_liability("Credit Card", 500)  // New API
            .hawl(true);
        
        // Total should be legacy + named = 1500
        assert_eq!(business.total_liabilities(), dec!(1500));
        
        let result = business.calculate_zakat(&config).unwrap();
        
        // Net = 10000 - 1500 = 8500
        // 8500 == Nisab, so payable
        assert!(result.is_payable);
        assert_eq!(result.net_assets, dec!(8500));
    }

    #[test]
    fn test_receivables_quality() {
        let config = ZakatConfig { gold_price_per_gram: Decimal::from(100), ..Default::default() };
        
        let business = BusinessZakat::new()
            .cash(5000.0)
            .inventory(3500.0) // 8500 total
            .add_receivable("Trusted Client", 1000.0, crate::debt::ReceivableQuality::Strong)
            .add_receivable("Bankrupt Client", 5000.0, crate::debt::ReceivableQuality::Weak)
            .hawl(true);

        let result = business.calculate_zakat(&config).unwrap();

        // 8500 (Base) + 1000 (Strong) = 9500 Net.  5000 Weak excluded.
        // Nisab = 85 * 100 = 8500. 9500 > 8500, so payable.
        assert!(result.is_payable);
        assert_eq!(result.net_assets, dec!(9500));
        assert_eq!(result.zakat_due, dec!(237.5)); // 9500 * 0.025
        
        // Check for note
        assert!(result.notes.iter().any(|n| n.contains("Excluded Weak Receivables")));
    }
    
    #[test]
    fn test_liability_struct() {
        use crate::types::Liability;
        
        let liability = Liability::new("Rent", dec!(2000));
        assert_eq!(liability.description, "Rent");
        assert_eq!(liability.amount, dec!(2000));
    }
}
