//! # 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, InvalidInputDetails};
use crate::math::ZakatDecimal;
use serde::{Serialize, Deserialize};
use crate::traits::{CalculateZakat, ZakatConfigArgument};

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

// Use the zakat_asset! macro to generate common fields and setters
crate::zakat_asset! {
    /// 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,
        pub receivables: Decimal,
        // Business-specific liabilities are now unified with `liabilities_due_now`
    }
}

impl Default for BusinessZakat {
    fn default() -> Self {
        let (liabilities_due_now, hawl_satisfied, label, id, _input_errors, acquisition_date) = Self::default_common();
        Self {
            cash_on_hand: Decimal::ZERO,
            inventory_value: Decimal::ZERO,
            receivables: Decimal::ZERO,
            liabilities_due_now,
            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)
    }

    /// 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()`.
    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
    }

    /// 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()`.
    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()
    }

    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
        if self.cash_on_hand < Decimal::ZERO || self.inventory_value < Decimal::ZERO || self.receivables < Decimal::ZERO {
            return Err(ZakatError::InvalidInput(Box::new(InvalidInputDetails {
                field: "business_assets".to_string(),
                value: "negative".to_string(),
                reason_key: "error-negative-value".to_string(),
                args: None,
                source_label: self.label.clone(),
                asset_id: None,
            })));
        }
        if self.liabilities_due_now < Decimal::ZERO {
             return Err(ZakatError::InvalidInput(Box::new(InvalidInputDetails {
                field: "liabilities".to_string(),
                value: "negative".to_string(),
                reason_key: "error-negative-value".to_string(),
                args: None,
                source_label: self.label.clone(),
                asset_id: None,
            })));
        }

        // 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 {
                reason_key: "error-gold-price-required".to_string(),
                args: None,
                source_label: self.label.clone(),
                asset_id: None,
            })));
        }
        if needs_silver && config.silver_price_per_gram <= Decimal::ZERO {
            return Err(ZakatError::ConfigurationError(Box::new(ErrorDetails {
                reason_key: "error-silver-price-required".to_string(),
                args: None,
                source_label: self.label.clone(),
                asset_id: None,
            })));
        }
        
        // 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;
        
        let gross_assets = ZakatDecimal::new(self.cash_on_hand)
            .safe_add(self.inventory_value)?
            .safe_add(self.receivables)?
            .with_source(self.label.clone());
        
        let trace_steps = vec![
            crate::types::CalculationStep::initial("step-cash-on-hand", "Cash on Hand", self.cash_on_hand),
            crate::types::CalculationStep::add("step-inventory-value", "Inventory Value", self.inventory_value),
            crate::types::CalculationStep::add("step-receivables", "Receivables", self.receivables),
            crate::types::CalculationStep::result("step-gross-assets", "Gross Assets", *gross_assets),
        ];



        // 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: self.liabilities_due_now,
            nisab_threshold: nisab_threshold_value,
            rate,
            wealth_type: crate::types::WealthType::Business,
            label: self.label.clone(),
            hawl_satisfied: hawl_is_satisfied,
            trace_steps,
        };

        calculate_monetary_asset(params)
    }

    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() };
        
        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() };
        
        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 { .. })));
    }
}
