//! Discount System
//!
//! Promotional, volume, and loyalty discounts.

use crate::{ServiceType, SynorDecimal};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};

/// Discount type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DiscountType {
    /// Percentage off
    Percentage,
    /// Fixed amount off
    FixedAmount,
    /// Volume-based (spend X, get Y% off)
    Volume,
    /// Loyalty reward
    Loyalty,
    /// Referral discount
    Referral,
    /// Promotional campaign
    Promotional,
    /// Partner/reseller discount
    Partner,
}

impl std::fmt::Display for DiscountType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DiscountType::Percentage => write!(f, "Percentage"),
            DiscountType::FixedAmount => write!(f, "Fixed Amount"),
            DiscountType::Volume => write!(f, "Volume"),
            DiscountType::Loyalty => write!(f, "Loyalty"),
            DiscountType::Referral => write!(f, "Referral"),
            DiscountType::Promotional => write!(f, "Promotional"),
            DiscountType::Partner => write!(f, "Partner"),
        }
    }
}

/// Discount definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Discount {
    /// Unique discount code/ID
    pub code: String,
    /// Discount name
    pub name: String,
    /// Description
    pub description: String,
    /// Discount type
    pub discount_type: DiscountType,
    /// Discount value (percentage or fixed amount)
    pub value: SynorDecimal,
    /// Minimum spend to qualify
    pub min_spend: Option<SynorDecimal>,
    /// Maximum discount amount
    pub max_discount: Option<SynorDecimal>,
    /// Applicable service types (None = all)
    pub service_types: Option<Vec<ServiceType>>,
    /// Applicable account IDs (None = all)
    pub account_ids: Option<Vec<String>>,
    /// Start date
    pub start_date: DateTime<Utc>,
    /// End date (None = no expiry)
    pub end_date: Option<DateTime<Utc>>,
    /// Maximum uses (None = unlimited)
    pub max_uses: Option<u32>,
    /// Current uses
    pub current_uses: u32,
    /// Active status
    pub is_active: bool,
}

impl Discount {
    /// Create a new percentage discount
    pub fn percentage(code: impl Into<String>, name: impl Into<String>, percentage: SynorDecimal) -> Self {
        Self {
            code: code.into(),
            name: name.into(),
            description: format!("{}% off", percentage),
            discount_type: DiscountType::Percentage,
            value: percentage,
            min_spend: None,
            max_discount: None,
            service_types: None,
            account_ids: None,
            start_date: Utc::now(),
            end_date: None,
            max_uses: None,
            current_uses: 0,
            is_active: true,
        }
    }

    /// Create a fixed amount discount
    pub fn fixed_amount(code: impl Into<String>, name: impl Into<String>, amount: SynorDecimal) -> Self {
        Self {
            code: code.into(),
            name: name.into(),
            description: format!("{} SYNOR off", amount),
            discount_type: DiscountType::FixedAmount,
            value: amount,
            min_spend: None,
            max_discount: Some(amount),
            service_types: None,
            account_ids: None,
            start_date: Utc::now(),
            end_date: None,
            max_uses: None,
            current_uses: 0,
            is_active: true,
        }
    }

    /// Create a volume discount
    pub fn volume(min_spend: SynorDecimal, percentage: SynorDecimal) -> Self {
        Self {
            code: format!("VOLUME_{}", min_spend),
            name: format!("Volume Discount ({} SYNOR+)", min_spend),
            description: format!("{}% off when spending {} SYNOR or more", percentage, min_spend),
            discount_type: DiscountType::Volume,
            value: percentage,
            min_spend: Some(min_spend),
            max_discount: None,
            service_types: None,
            account_ids: None,
            start_date: Utc::now(),
            end_date: None,
            max_uses: None,
            current_uses: 0,
            is_active: true,
        }
    }

    /// Create a referral discount
    pub fn referral(referrer_id: impl Into<String>) -> Self {
        let referrer = referrer_id.into();
        Self {
            code: format!("REF_{}", &referrer[..8.min(referrer.len())].to_uppercase()),
            name: "Referral Discount".to_string(),
            description: "10% off for referred users".to_string(),
            discount_type: DiscountType::Referral,
            value: Decimal::new(10, 0), // 10%
            min_spend: None,
            max_discount: Some(Decimal::new(50, 0)), // Max 50 SYNOR
            service_types: None,
            account_ids: None,
            start_date: Utc::now(),
            end_date: Some(Utc::now() + chrono::Duration::days(90)),
            max_uses: Some(1),
            current_uses: 0,
            is_active: true,
        }
    }

    /// Set expiration
    pub fn with_expiry(mut self, end_date: DateTime<Utc>) -> Self {
        self.end_date = Some(end_date);
        self
    }

    /// Set expiration days from now
    pub fn with_expiry_days(mut self, days: i64) -> Self {
        self.end_date = Some(Utc::now() + chrono::Duration::days(days));
        self
    }

    /// Set minimum spend
    pub fn with_min_spend(mut self, min: SynorDecimal) -> Self {
        self.min_spend = Some(min);
        self
    }

    /// Set maximum discount
    pub fn with_max_discount(mut self, max: SynorDecimal) -> Self {
        self.max_discount = Some(max);
        self
    }

    /// Limit to specific service types
    pub fn for_services(mut self, services: Vec<ServiceType>) -> Self {
        self.service_types = Some(services);
        self
    }

    /// Limit to specific accounts
    pub fn for_accounts(mut self, accounts: Vec<String>) -> Self {
        self.account_ids = Some(accounts);
        self
    }

    /// Set maximum uses
    pub fn with_max_uses(mut self, max: u32) -> Self {
        self.max_uses = Some(max);
        self
    }

    /// Check if discount is currently valid
    pub fn is_valid(&self) -> bool {
        if !self.is_active {
            return false;
        }

        let now = Utc::now();

        if now < self.start_date {
            return false;
        }

        if let Some(end) = self.end_date {
            if now > end {
                return false;
            }
        }

        if let Some(max) = self.max_uses {
            if self.current_uses >= max {
                return false;
            }
        }

        true
    }

    /// Check if discount is applicable to an account and service
    pub fn is_applicable(&self, account_id: &str, service_type: Option<ServiceType>) -> bool {
        if !self.is_valid() {
            return false;
        }

        // Check account restriction
        if let Some(ref accounts) = self.account_ids {
            if !accounts.iter().any(|a| a == account_id) {
                return false;
            }
        }

        // Check service restriction
        if let Some(ref services) = self.service_types {
            if let Some(st) = service_type {
                if !services.contains(&st) {
                    return false;
                }
            }
        }

        true
    }

    /// Calculate discount amount for a given spend
    pub fn calculate_discount(&self, amount: SynorDecimal) -> SynorDecimal {
        // Check minimum spend
        if let Some(min) = self.min_spend {
            if amount < min {
                return Decimal::ZERO;
            }
        }

        let discount = match self.discount_type {
            DiscountType::Percentage | DiscountType::Volume | DiscountType::Loyalty | DiscountType::Referral | DiscountType::Partner => {
                amount * (self.value / Decimal::ONE_HUNDRED)
            }
            DiscountType::FixedAmount | DiscountType::Promotional => {
                self.value.min(amount) // Can't discount more than amount
            }
        };

        // Apply maximum discount cap
        if let Some(max) = self.max_discount {
            discount.min(max)
        } else {
            discount
        }
    }

    /// Use the discount (increment counter)
    pub fn use_discount(&mut self) -> bool {
        if !self.is_valid() {
            return false;
        }

        self.current_uses += 1;

        // Check if now exhausted
        if let Some(max) = self.max_uses {
            if self.current_uses >= max {
                self.is_active = false;
            }
        }

        true
    }
}

/// Volume discount tiers
pub fn standard_volume_discounts() -> Vec<Discount> {
    vec![
        Discount::volume(Decimal::new(100, 0), Decimal::new(5, 0)),  // 5% at 100+ SYNOR
        Discount::volume(Decimal::new(500, 0), Decimal::new(10, 0)), // 10% at 500+ SYNOR
        Discount::volume(Decimal::new(1000, 0), Decimal::new(15, 0)), // 15% at 1000+ SYNOR
        Discount::volume(Decimal::new(5000, 0), Decimal::new(20, 0)), // 20% at 5000+ SYNOR
    ]
}

/// Find best applicable volume discount
pub fn find_best_volume_discount(amount: SynorDecimal) -> Option<Discount> {
    standard_volume_discounts()
        .into_iter()
        .filter(|d| {
            d.min_spend
                .map(|min| amount >= min)
                .unwrap_or(false)
        })
        .max_by(|a, b| a.value.cmp(&b.value))
}

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

    #[test]
    fn test_percentage_discount() {
        let discount = Discount::percentage("TEST10", "Test Discount", dec!(10));

        assert!(discount.is_valid());

        let amount = discount.calculate_discount(dec!(100));
        assert_eq!(amount, dec!(10)); // 10% of 100
    }

    #[test]
    fn test_fixed_discount() {
        let discount = Discount::fixed_amount("FIXED50", "Fixed 50", dec!(50));

        // Normal case
        let amount = discount.calculate_discount(dec!(100));
        assert_eq!(amount, dec!(50));

        // Amount less than discount
        let amount = discount.calculate_discount(dec!(30));
        assert_eq!(amount, dec!(30)); // Can't exceed original amount
    }

    #[test]
    fn test_volume_discount() {
        let discount = Discount::volume(dec!(100), dec!(10)).with_min_spend(dec!(100));

        // Below minimum
        let amount = discount.calculate_discount(dec!(50));
        assert_eq!(amount, dec!(0));

        // Above minimum
        let amount = discount.calculate_discount(dec!(200));
        assert_eq!(amount, dec!(20)); // 10% of 200
    }

    #[test]
    fn test_discount_max_cap() {
        let discount = Discount::percentage("CAPPED", "Capped Discount", dec!(50))
            .with_max_discount(dec!(100));

        // Would be 250 but capped at 100
        let amount = discount.calculate_discount(dec!(500));
        assert_eq!(amount, dec!(100));
    }

    #[test]
    fn test_discount_expiry() {
        let discount = Discount::percentage("EXPIRED", "Expired", dec!(10))
            .with_expiry(Utc::now() - chrono::Duration::days(1));

        assert!(!discount.is_valid());
    }

    #[test]
    fn test_discount_usage_limit() {
        let mut discount = Discount::percentage("LIMITED", "Limited Use", dec!(10))
            .with_max_uses(2);

        assert!(discount.use_discount());
        assert!(discount.use_discount());
        assert!(!discount.use_discount()); // Third use should fail
    }

    #[test]
    fn test_find_best_volume_discount() {
        let best = find_best_volume_discount(dec!(750));
        assert!(best.is_some());
        assert_eq!(best.unwrap().value, dec!(10)); // 10% tier (500+)

        let best_high = find_best_volume_discount(dec!(6000));
        assert!(best_high.is_some());
        assert_eq!(best_high.unwrap().value, dec!(20)); // 20% tier (5000+)
    }
}
