/**
 * Discount Calculation Service
 * Business logic for discount validation and calculation
 */

// Import Discount and related models
const { Discount, DiscountRule, Sale, Product, ProductCategory, Category, Customer } = require('../../../models');
// Import custom error classes
const { NotFoundError, ValidationError } = require('../../../utils/errors');
// Import logger for logging
const logger = require('../../../utils/logger');
// Import Sequelize operators
const { Op } = require('sequelize');

/**
 * Validate discount
 * Validates if a discount can be applied (dates, usage limits, active status)
 * Note: Since customer registration is removed, all sales are walk-in (customerId = null)
 * Per-customer usage limits only apply if customerId is provided (future feature)
 * @param {Object} discount - Discount object
 * @param {number} customerId - Optional customer ID for per-customer limits (null for walk-in customers)
 * @returns {Promise<Object>} Validation result with isValid flag and message
 */
const validateDiscount = async (discount, customerId = null) => {
  // Check if discount is active
  if (!discount.active) {
    return {
      isValid: false, // Discount is not valid
      message: 'Discount is not active', // Error message
    };
  }
  
  // Check effective dates
  const now = new Date(); // Current date
  if (new Date(discount.effective_from) > now) {
    return {
      isValid: false, // Discount is not valid
      message: 'Discount has not started yet', // Error message
    };
  }
  
  if (discount.effective_to && new Date(discount.effective_to) < now) {
    return {
      isValid: false, // Discount is not valid
      message: 'Discount has expired', // Error message
    };
  }
  
  // Check total usage limit
  if (discount.max_total_uses !== null && discount.usage_count >= discount.max_total_uses) {
    return {
      isValid: false, // Discount is not valid
      message: 'Discount usage limit has been reached', // Error message
    };
  }
  
  // Check per-customer usage limit if customer ID provided
  if (customerId && discount.max_uses_per_customer !== null) {
    // Count sales with this discount for this customer
    const customerUsageCount = await Sale.count({
      where: {
        customer_id: customerId, // Filter by customer ID
        discount_id: discount.id, // Filter by discount ID
      },
    });
    
    if (customerUsageCount >= discount.max_uses_per_customer) {
      return {
        isValid: false, // Discount is not valid
        message: 'Customer has reached the usage limit for this discount', // Error message
      };
    }
  }
  
  // Discount is valid
  return {
    isValid: true, // Discount is valid
    message: 'Discount is valid', // Success message
  };
};

/**
 * Check if discount applies to product
 * Checks if a discount rule applies to a specific product
 * Supports per-product discounts (PRODUCT rule type) and per-group discounts (CATEGORY rule type)
 * @param {Object} rule - Discount rule
 * @param {number} productId - Product ID (variants removed - products only)
 * @returns {Promise<boolean>} True if discount applies
 */
const checkDiscountRuleForProduct = async (rule, productId) => {
  // If rule type is ALL, it applies to all products
  if (rule.rule_type === 'ALL') {
    return true; // Applies to all products
  }
  
  // If rule type is PRODUCT, check if product matches (per-product discount)
  if (rule.rule_type === 'PRODUCT') {
    return rule.product_id === productId; // Return true if product matches
  }
  
  // If rule type is CATEGORY, check if product belongs to category (per-group discount)
  if (rule.rule_type === 'CATEGORY') {
    // Find if product is in this category
    const productCategory = await ProductCategory.findOne({
      where: {
        product_id: productId, // Filter by product ID
        category_id: rule.category_id, // Filter by category ID
      },
    });
    
    if (productCategory) {
      return true; // Product is in category
    }
    
    // Also check parent categories (hierarchical)
    const category = await Category.findByPk(rule.category_id); // Find category
    if (category && category.parent_id) {
      // Recursively check parent categories
      const parentRule = {
        ...rule, // Copy rule
        category_id: category.parent_id, // Use parent category ID
      };
      return await checkDiscountRuleForProduct(parentRule, productId); // Check parent category
    }
    
    return false; // Product is not in category
  }
  
  // Rule type CUSTOMER is checked separately (not product-specific)
  return false; // Default to false
};

/**
 * Check if discount applies to customer
 * Checks if a discount rule applies to a specific customer
 * Note: Since customer registration is removed, all sales are walk-in (customerId = null)
 * CUSTOMER rules will NOT apply to walk-in customers (only ALL, PRODUCT, CATEGORY rules apply)
 * @param {Object} rule - Discount rule
 * @param {number} customerId - Customer ID (null for walk-in customers - all current sales)
 * @returns {boolean} True if discount applies
 */
const checkDiscountRuleForCustomer = (rule, customerId) => {
  // If rule type is ALL, it applies to all customers (including walk-ins)
  if (rule.rule_type === 'ALL') {
    return true; // Applies to all customers including walk-ins
  }
  
  // If rule type is CUSTOMER, check if customer matches
  if (rule.rule_type === 'CUSTOMER') {
    // Since all sales are walk-in (customerId = null), CUSTOMER rules don't apply
    // This is by design - customer-specific discounts are not supported for walk-in customers
    if (!customerId) {
      return false; // Customer rule doesn't apply to walk-ins
    }
    return rule.customer_id === customerId; // Return true if customer matches (future feature)
  }
  
  // PRODUCT and CATEGORY rules don't restrict by customer
  // They apply to all customers (including walk-ins) unless there's also a CUSTOMER rule
  return true; // Default to true for product/category rules (applies to walk-ins)
};

/**
 * Calculate discount amount
 * Calculates the discount amount for a given base amount
 * @param {Object} discount - Discount object
 * @param {number} baseAmount - Base amount to apply discount to
 * @param {number} quantity - Optional quantity for BUY_X_GET_Y
 * @returns {Object} Calculated discount with amount and details
 */
const calculateDiscountAmount = (discount, baseAmount, quantity = 1) => {
  let discountAmount = 0; // Initialize discount amount
  let details = {}; // Initialize details object
  
  // Calculate based on discount type
  if (discount.discount_type === 'PERCENTAGE') {
    // Percentage discount: (baseAmount * discount_value) / 100
    discountAmount = (baseAmount * parseFloat(discount.discount_value)) / 100; // Calculate percentage discount
    
    // Apply maximum discount amount if specified
    if (discount.max_discount_amount !== null && discountAmount > discount.max_discount_amount) {
      discountAmount = parseFloat(discount.max_discount_amount); // Cap at maximum
      details.capped = true; // Flag that discount was capped
    }
    
    details.type = 'PERCENTAGE'; // Set type
    details.percentage = parseFloat(discount.discount_value); // Store percentage
  } else if (discount.discount_type === 'FIXED_AMOUNT') {
    // Fixed amount discount: discount_value (capped at baseAmount)
    discountAmount = parseFloat(discount.discount_value); // Use discount value as fixed amount
    if (discountAmount > baseAmount) {
      discountAmount = baseAmount; // Cap at base amount
      details.capped = true; // Flag that discount was capped
    }
    
    details.type = 'FIXED_AMOUNT'; // Set type
    details.fixedAmount = discountAmount; // Store fixed amount
  } else if (discount.discount_type === 'BUY_X_GET_Y') {
    // Buy X Get Y: Calculate free items based on quantity
    // discount_value is the Y (number of free items)
    // We need to determine X from rules (min_quantity), or assume buy 1 get discount_value free
    // For simplicity, assume: buy 1 get Y free (where Y = discount_value)
    // This is calculated at item level, so we check quantity
    const freeQuantity = Math.floor(quantity / 1) * parseFloat(discount.discount_value); // Calculate free quantity (buy 1 get Y free)
    const itemPrice = baseAmount / quantity; // Calculate item price
    discountAmount = freeQuantity * itemPrice; // Calculate discount amount (free items * price)
    
    details.type = 'BUY_X_GET_Y'; // Set type
    details.freeQuantity = freeQuantity; // Store free quantity
    details.buyQuantity = 1; // Store buy quantity (simplified)
  }
  
  // Round discount amount to 2 decimal places
  discountAmount = parseFloat(discountAmount.toFixed(2)); // Round to 2 decimals
  
  // Return calculated discount
  return {
    discountAmount, // Discount amount
    details, // Discount details
  };
};

/**
 * Apply discount to sale
 * Applies a discount to sale items and returns discount details
 * Supports per-product discounts (PRODUCT rule type) and per-group discounts (CATEGORY rule type)
 * Note: All sales are walk-in customers (customerId = null), so CUSTOMER rules won't apply
 * @param {Object} discount - Discount object
 * @param {Array} saleItems - Array of sale items
 * @param {number} customerId - Optional customer ID (null for walk-in customers - all current sales)
 * @returns {Promise<Object>} Discount application result with items and totals
 */
const applyDiscount = async (discount, saleItems, customerId = null) => {
  // Validate discount first
  const validation = await validateDiscount(discount, customerId); // Validate discount
  if (!validation.isValid) {
    throw new ValidationError(validation.message); // Throw error if invalid
  }
  
  // Get discount rules
  const rules = await DiscountRule.findAll({
    where: { discount_id: discount.id }, // Filter by discount ID
  });
  
  // If no rules, discount applies to all items (default behavior)
  const hasRules = rules && rules.length > 0;
  
  // Calculate subtotal first
  const subtotal = saleItems.reduce((sum, item) => {
    return sum + (parseFloat(item.quantity) * parseFloat(item.unit_price)); // Sum quantity * unit_price
  }, 0); // Initialize sum to 0
  
  // Check minimum purchase amount
  if (discount.min_purchase_amount && subtotal < parseFloat(discount.min_purchase_amount || 0)) {
    throw new ValidationError(`Minimum purchase amount of ${discount.min_purchase_amount} required for this discount`); // Throw error if minimum not met
  }
  
  // Process each sale item
  const discountedItems = []; // Initialize discounted items array
  let totalDiscountAmount = 0; // Initialize total discount amount
  
  for (const item of saleItems) {
    // Check if any rule applies to this item (or if no rules, apply to all)
    let itemApplies = false; // Initialize flag
    let minQuantity = 0; // Default minimum quantity (0 means no minimum)
    
    if (!hasRules) {
      // No rules = discount applies to all items
      itemApplies = true; // Apply to all items
      minQuantity = 0; // No minimum quantity requirement
      logger.info('Discount with no rules - applying to all items', { 
        productId: item.product_id, 
        quantity: item.quantity 
      });
    } else {
      // Check each rule to see if it applies
      logger.info('Checking rules for item', { 
        productId: item.product_id, 
        
        quantity: item.quantity,
        rulesCount: rules.length,
        customerId 
      });
      
      // Separate rules by type for better logic
      const allRules = rules.filter(r => r.rule_type === 'ALL');
      const productRules = rules.filter(r => r.rule_type === 'PRODUCT' || r.rule_type === 'CATEGORY');
      const customerRules = rules.filter(r => r.rule_type === 'CUSTOMER');
      
      logger.info('Rules breakdown', {
        allRulesCount: allRules.length,
        productRulesCount: productRules.length,
        customerRulesCount: customerRules.length
      });
      
      // If there's an ALL rule, check it first
      if (allRules.length > 0) {
        for (const rule of allRules) {
          const ruleMinQty = parseFloat(rule.min_quantity || 0);
          const itemQty = parseFloat(item.quantity);
          if (itemQty >= ruleMinQty) {
            itemApplies = true;
            minQuantity = ruleMinQty;
            logger.info('ALL rule matched', { ruleId: rule.id });
            break;
          }
        }
      }
      
      // If no ALL rule matched, check product/category and customer rules
      if (!itemApplies) {
        // Check if any product/category rule matches
        let productMatches = false;
        for (const rule of productRules) {
          const productApplies = await checkDiscountRuleForProduct(
            rule,
            item.product_id,
            
          );
          
          logger.info('Product rule check', {
            ruleId: rule.id,
            ruleType: rule.rule_type,
            productApplies,
            productId: item.product_id,
            ruleProductId: rule.product_id,
            ruleCategoryId: rule.category_id
          });
          
          if (productApplies) {
            const ruleMinQty = parseFloat(rule.min_quantity || 0);
            const itemQty = parseFloat(item.quantity);
            if (itemQty >= ruleMinQty) {
              productMatches = true;
              minQuantity = ruleMinQty;
              logger.info('Product rule matched', { ruleId: rule.id });
              break;
            }
          }
        }
        
        // Check if customer rules apply (or if there are no customer rules, assume all customers)
        let customerMatches = true; // Default to true if no customer rules
        if (customerRules.length > 0) {
          customerMatches = false;
          for (const rule of customerRules) {
            const customerApplies = checkDiscountRuleForCustomer(rule, customerId);
            logger.info('Customer rule check', {
              ruleId: rule.id,
              customerApplies,
              customerId,
              ruleCustomerId: rule.customer_id
            });
            if (customerApplies) {
              customerMatches = true;
              logger.info('Customer rule matched', { ruleId: rule.id });
              break;
            }
          }
        }
        
        // Item applies if product matches AND customer matches
        if (productMatches && customerMatches) {
          itemApplies = true;
          logger.info('Item applies - product and customer rules matched');
        } else if (productRules.length === 0 && customerMatches) {
          // If no product rules, but customer matches (or no customer rules), apply to all products
          itemApplies = true;
          logger.info('Item applies - no product rules, customer allowed');
        } else {
          logger.info('Item does not apply', {
            productMatches,
            customerMatches,
            hasProductRules: productRules.length > 0,
            hasCustomerRules: customerRules.length > 0
          });
        }
      }
    }
    
    // Calculate discount for this item if it applies
    if (itemApplies) {
      const lineTotal = parseFloat(item.quantity) * parseFloat(item.unit_price); // Calculate line total
      logger.info('Calculating discount for item', {
        productId: item.product_id,
        lineTotal,
        quantity: item.quantity,
        unitPrice: item.unit_price,
        discountType: discount.discount_type,
        discountValue: discount.discount_value
      });
      
      const discountResult = calculateDiscountAmount(
        discount, // Discount object
        lineTotal, // Base amount
        parseFloat(item.quantity) // Quantity
      ); // Calculate discount
      
      logger.info('Discount calculation result', {
        productId: item.product_id,
        discountAmount: discountResult.discountAmount,
        details: discountResult.details
      });
      
      discountedItems.push({
        ...item, // Copy item data
        discountAmount: discountResult.discountAmount, // Discount amount for this item
        discountDetails: discountResult.details, // Discount details
      }); // Add to discounted items
      
      totalDiscountAmount += discountResult.discountAmount; // Add to total discount
    } else {
      logger.info('Discount does not apply to item', {
        productId: item.product_id,
        hasRules,
        rulesCount: rules.length
      });
      // No discount for this item
      discountedItems.push({
        ...item, // Copy item data
        discountAmount: 0, // No discount
        discountDetails: null, // No details
      }); // Add to discounted items
    }
  }
  
  logger.info('Discount application summary', {
    totalDiscountAmount,
    itemsProcessed: saleItems.length,
    discountedItemsCount: discountedItems.filter(i => i.discountAmount > 0).length,
    hasRules,
    rulesCount: rules.length
  });
  
  // If no items were discounted, discount doesn't apply
  if (totalDiscountAmount === 0) {
    logger.error('Discount does not apply to any items', {
      hasRules,
      rulesCount: rules.length,
      items: saleItems.map(i => ({ productId: i.product_id, quantity: i.quantity }))
    });
    throw new ValidationError('Discount does not apply to any items in this sale'); // Throw error if no items discounted
  }
  
  // Return discount application result
  return {
    discountId: discount.id, // Discount ID
    discountCode: discount.code, // Discount code
    discountName: discount.name, // Discount name
    discountType: discount.discount_type, // Discount type
    items: discountedItems, // Discounted items
    totalDiscountAmount: parseFloat(totalDiscountAmount.toFixed(2)), // Total discount amount
    originalSubtotal: parseFloat(subtotal.toFixed(2)), // Original subtotal
    discountedSubtotal: parseFloat((subtotal - totalDiscountAmount).toFixed(2)), // Discounted subtotal
  };
};

// Export discount calculation service functions
module.exports = {
  validateDiscount,
  checkDiscountRuleForProduct,
  checkDiscountRuleForCustomer,
  calculateDiscountAmount,
  applyDiscount,
};

