/**
 * Discount Service
 * Business logic for discount management
 */

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

/**
 * Create discount
 * Creates a new discount with optional rules
 * @param {Object} discountData - Discount data (name, code, discount_type, discount_value, etc.)
 * @param {Array} rules - Optional array of discount rules
 * @returns {Promise<Object>} Created discount with rules
 */
const createDiscount = async (discountData, rules = []) => {
  // Extract discount data
  const {
    name, // Discount name
    code = null, // Discount code (optional)
    discount_type, // Discount type (PERCENTAGE, FIXED_AMOUNT, BUY_X_GET_Y)
    discount_value, // Discount value
    min_purchase_amount = 0, // Minimum purchase amount
    max_discount_amount = null, // Maximum discount amount
    effective_from = new Date(), // Effective from date
    effective_to = null, // Effective to date
    max_uses_per_customer = null, // Max uses per customer
    max_total_uses = null, // Max total uses
    active = true, // Active status
    description = null, // Description
  } = discountData; // Extract data
  
  // Validate required fields
  if (!name) {
    throw new ValidationError('Discount name is required'); // Throw error if name missing
  }
  
  if (!discount_type) {
    throw new ValidationError('Discount type is required'); // Throw error if type missing
  }
  
  if (!discount_value && discount_value !== 0) {
    throw new ValidationError('Discount value is required'); // Throw error if value missing
  }
  
  // Validate discount type
  const validTypes = ['PERCENTAGE', 'FIXED_AMOUNT', 'BUY_X_GET_Y']; // Valid discount types
  if (!validTypes.includes(discount_type)) {
    throw new ValidationError(`Invalid discount type. Must be one of: ${validTypes.join(', ')}`); // Throw error if invalid type
  }
  
  // Validate discount value based on type
  if (discount_type === 'PERCENTAGE') {
    // For percentage, value should be between 0 and 100
    if (discount_value < 0 || discount_value > 100) {
      throw new ValidationError('Percentage discount value must be between 0 and 100'); // Throw error if invalid percentage
    }
  } else if (discount_type === 'FIXED_AMOUNT') {
    // For fixed amount, value must be positive
    if (discount_value <= 0) {
      throw new ValidationError('Fixed amount discount value must be greater than 0'); // Throw error if invalid amount
    }
  } else if (discount_type === 'BUY_X_GET_Y') {
    // For buy X get Y, value represents the Y quantity (how many free)
    if (discount_value <= 0) {
      throw new ValidationError('Buy X Get Y discount value (Y quantity) must be greater than 0'); // Throw error if invalid quantity
    }
  }
  
  // Validate date range
  if (effective_to && new Date(effective_to) <= new Date(effective_from)) {
    throw new ValidationError('Effective end date must be after start date'); // Throw error if invalid date range
  }
  
  // Check for duplicate code if provided
  if (code) {
    const existingDiscount = await Discount.findOne({
      where: { code }, // Find by code
    });
    
    if (existingDiscount) {
      throw new ConflictError(`Discount with code "${code}" already exists`); // Throw error if code exists
    }
  }
  
  // Create discount
  const discount = await Discount.create({
    name, // Discount name
    code, // Discount code
    discount_type, // Discount type
    discount_value, // Discount value
    min_purchase_amount, // Minimum purchase amount
    max_discount_amount, // Maximum discount amount
    effective_from: new Date(effective_from), // Effective from date
    effective_to: effective_to ? new Date(effective_to) : null, // Effective to date
    max_uses_per_customer, // Max uses per customer
    max_total_uses, // Max total uses
    usage_count: 0, // Initial usage count
    active, // Active status
    description, // Description
  });
  
  // Create discount rules if provided
  if (rules && rules.length > 0) {
    // Validate rules
    for (const rule of rules) {
      const { rule_type, product_id, category_id, customer_id, min_quantity = 1 } = rule; // Extract rule data
      
      // Validate rule type
      const validRuleTypes = ['PRODUCT', 'CATEGORY', 'CUSTOMER', 'ALL']; // Valid rule types
      if (!validRuleTypes.includes(rule_type)) {
        throw new ValidationError(`Invalid rule type. Must be one of: ${validRuleTypes.join(', ')}`); // Throw error if invalid type
      }
      
      // Validate rule-specific fields
      if (rule_type === 'PRODUCT' && !product_id) {
        throw new ValidationError('Product ID is required for PRODUCT rule type'); // Throw error if product_id missing
      }
      
      if (rule_type === 'CATEGORY' && !category_id) {
        throw new ValidationError('Category ID is required for CATEGORY rule type'); // Throw error if category_id missing
      }
      
      if (rule_type === 'CUSTOMER' && !customer_id) {
        throw new ValidationError('Customer ID is required for CUSTOMER rule type'); // Throw error if customer_id missing
      }
      
      // Verify referenced entities exist
      if (product_id) {
        const product = await Product.findByPk(product_id); // Find product
        if (!product) {
          throw new NotFoundError(`Product with ID ${product_id} not found`); // Throw error if product not found
        }
      }
      
      if (category_id) {
        const category = await Category.findByPk(category_id); // Find category
        if (!category) {
          throw new NotFoundError(`Category with ID ${category_id} not found`); // Throw error if category not found
        }
      }
      
      if (customer_id) {
        const customer = await Customer.findByPk(customer_id); // Find customer
        if (!customer) {
          throw new NotFoundError(`Customer with ID ${customer_id} not found`); // Throw error if customer not found
        }
      }
    }
    
    // Create rules
    const rulePromises = rules.map(rule => {
      const { rule_type, product_id, category_id, customer_id, min_quantity = 1 } = rule; // Extract rule data
      return DiscountRule.create({
        discount_id: discount.id, // Discount ID
        rule_type, // Rule type
        product_id: rule_type === 'PRODUCT' ? product_id : null, // Product ID if applicable
        category_id: rule_type === 'CATEGORY' ? category_id : null, // Category ID if applicable
        customer_id: rule_type === 'CUSTOMER' ? customer_id : null, // Customer ID if applicable
        min_quantity, // Minimum quantity
      });
    });
    
    await Promise.all(rulePromises); // Create all rules
  }
  
  // Reload discount with rules
  const discountWithRules = await Discount.findByPk(discount.id, {
    include: [
      {
        model: DiscountRule, // Include discount rules
        as: 'rules', // Use rules alias
        include: [
          {
            model: Product, // Include product if applicable
            as: 'product', // Use product alias
            required: false, // Left join
          },
          {
            model: Category, // Include category if applicable
            as: 'category', // Use category alias
            required: false, // Left join
          },
          {
            model: Customer, // Include customer if applicable
            as: 'customer', // Use customer alias
            required: false, // Left join
          },
        ],
      },
    ],
  });
  
  logger.info(`Discount created: ${discount.id} - ${discount.name}`); // Log discount creation
  
  // Return discount with rules
  return discountWithRules;
};

/**
 * Get discount by ID
 * Retrieves a discount by ID with rules
 * @param {number} discountId - Discount ID
 * @returns {Promise<Object>} Discount with rules
 */
const getDiscount = async (discountId) => {
  // Validate discount ID
  if (!discountId) {
    throw new ValidationError('Discount ID is required'); // Throw error if ID missing
  }
  
  // Find discount
  const discount = await Discount.findByPk(discountId, {
    include: [
      {
        model: DiscountRule, // Include discount rules
        as: 'rules', // Use rules alias
        include: [
          {
            model: Product, // Include product if applicable
            as: 'product', // Use product alias
            required: false, // Left join
          },
          {
            model: Category, // Include category if applicable
            as: 'category', // Use category alias
            required: false, // Left join
          },
          {
            model: Customer, // Include customer if applicable
            as: 'customer', // Use customer alias
            required: false, // Left join
          },
        ],
      },
    ],
  });
  
  // Check if discount exists
  if (!discount) {
    throw new NotFoundError(`Discount with ID ${discountId} not found`); // Throw error if not found
  }
  
  // Return discount
  return discount;
};

/**
 * Get discount by code
 * Retrieves a discount by code with rules
 * @param {string} code - Discount code
 * @returns {Promise<Object>} Discount with rules
 */
const getDiscountByCode = async (code) => {
  // Validate code
  if (!code) {
    throw new ValidationError('Discount code is required'); // Throw error if code missing
  }
  
  // Find discount by code
  const discount = await Discount.findOne({
    where: { code }, // Find by code
    include: [
      {
        model: DiscountRule, // Include discount rules
        as: 'rules', // Use rules alias
        include: [
          {
            model: Product, // Include product if applicable
            as: 'product', // Use product alias
            required: false, // Left join
          },
          {
            model: Category, // Include category if applicable
            as: 'category', // Use category alias
            required: false, // Left join
          },
          {
            model: Customer, // Include customer if applicable
            as: 'customer', // Use customer alias
            required: false, // Left join
          },
        ],
      },
    ],
  });
  
  // Check if discount exists
  if (!discount) {
    throw new NotFoundError(`Discount with code "${code}" not found`); // Throw error if not found
  }
  
  // Return discount
  return discount;
};

/**
 * Update discount
 * Updates an existing discount
 * @param {number} discountId - Discount ID
 * @param {Object} updateData - Update data
 * @returns {Promise<Object>} Updated discount
 */
const updateDiscount = async (discountId, updateData) => {
  // Validate discount ID
  if (!discountId) {
    throw new ValidationError('Discount ID is required'); // Throw error if ID missing
  }
  
  // Find discount
  const discount = await Discount.findByPk(discountId); // Find by ID
  
  // Check if discount exists
  if (!discount) {
    throw new NotFoundError(`Discount with ID ${discountId} not found`); // Throw error if not found
  }
  
  // Extract update data
  const {
    name, // Discount name
    code, // Discount code
    discount_type, // Discount type
    discount_value, // Discount value
    min_purchase_amount, // Minimum purchase amount
    max_discount_amount, // Maximum discount amount
    effective_from, // Effective from date
    effective_to, // Effective to date
    max_uses_per_customer, // Max uses per customer
    max_total_uses, // Max total uses
    active, // Active status
    description, // Description
  } = updateData; // Extract data
  
  // Validate discount value if provided
  if (discount_value !== undefined) {
    const discountType = discount_type || discount.discount_type; // Use provided type or existing
    if (discountType === 'PERCENTAGE') {
      if (discount_value < 0 || discount_value > 100) {
        throw new ValidationError('Percentage discount value must be between 0 and 100'); // Throw error if invalid percentage
      }
    } else if (discountType === 'FIXED_AMOUNT' || discountType === 'BUY_X_GET_Y') {
      if (discount_value <= 0) {
        throw new ValidationError(`${discountType} discount value must be greater than 0`); // Throw error if invalid value
      }
    }
  }
  
  // Validate date range if dates are being updated
  const effectiveFrom = effective_from ? new Date(effective_from) : discount.effective_from; // Use provided or existing
  const effectiveTo = effective_to ? new Date(effective_to) : discount.effective_to; // Use provided or existing
  if (effectiveTo && effectiveTo <= effectiveFrom) {
    throw new ValidationError('Effective end date must be after start date'); // Throw error if invalid date range
  }
  
  // Check for duplicate code if code is being changed
  if (code && code !== discount.code) {
    const existingDiscount = await Discount.findOne({
      where: { code }, // Find by code
    });
    
    if (existingDiscount) {
      throw new ConflictError(`Discount with code "${code}" already exists`); // Throw error if code exists
    }
  }
  
  // Update discount
  await discount.update({
    ...(name !== undefined && { name }), // Update name if provided
    ...(code !== undefined && { code }), // Update code if provided
    ...(discount_type !== undefined && { discount_type }), // Update type if provided
    ...(discount_value !== undefined && { discount_value }), // Update value if provided
    ...(min_purchase_amount !== undefined && { min_purchase_amount }), // Update min purchase if provided
    ...(max_discount_amount !== undefined && { max_discount_amount }), // Update max discount if provided
    ...(effective_from !== undefined && { effective_from: new Date(effective_from) }), // Update from date if provided
    ...(effective_to !== undefined && { effective_to: effective_to ? new Date(effective_to) : null }), // Update to date if provided
    ...(max_uses_per_customer !== undefined && { max_uses_per_customer }), // Update max uses per customer if provided
    ...(max_total_uses !== undefined && { max_total_uses }), // Update max total uses if provided
    ...(active !== undefined && { active }), // Update active status if provided
    ...(description !== undefined && { description }), // Update description if provided
  });
  
  logger.info(`Discount updated: ${discount.id} - ${discount.name}`); // Log discount update
  
  // Return updated discount with rules
  return await getDiscount(discountId); // Return discount with rules
};

/**
 * Delete discount
 * Deletes a discount (and its rules via cascade)
 * @param {number} discountId - Discount ID
 * @returns {Promise<void>}
 */
const deleteDiscount = async (discountId) => {
  // Validate discount ID
  if (!discountId) {
    throw new ValidationError('Discount ID is required'); // Throw error if ID missing
  }
  
  // Find discount
  const discount = await Discount.findByPk(discountId); // Find by ID
  
  // Check if discount exists
  if (!discount) {
    throw new NotFoundError(`Discount with ID ${discountId} not found`); // Throw error if not found
  }
  
  // Delete discount (rules will be cascade deleted)
  await discount.destroy(); // Delete discount
  
  logger.info(`Discount deleted: ${discountId}`); // Log discount deletion
};

/**
 * List discounts
 * Lists discounts with optional filters
 * @param {Object} filters - Filter options (active, discount_type, etc.)
 * @param {Object} pagination - Pagination options (page, limit)
 * @returns {Promise<Object>} Paginated list of discounts
 */
const listDiscounts = async (filters = {}, pagination = {}) => {
  // Extract filters
  const {
    active, // Active status filter
    discount_type, // Discount type filter
    search, // Search term (name or code)
  } = filters; // Extract filters
  
  // Extract pagination options
  const page = parseInt(pagination.page, 10) || 1; // Current page (default 1)
  const limit = parseInt(pagination.limit, 10) || 50; // Items per page (default 50)
  const offset = (page - 1) * limit; // Calculate offset
  
  // Build where clause
  const where = {}; // Initialize where clause
  
  // Add active filter
  if (active !== undefined) {
    where.active = active; // Filter by active status
  }
  
  // Add discount type filter
  if (discount_type) {
    where.discount_type = discount_type; // Filter by discount type
  }
  
  // Add search filter
  if (search) {
    where[Op.or] = [
      { name: { [Op.like]: `%${search}%` } }, // Search in name
      { code: { [Op.like]: `%${search}%` } }, // Search in code
    ];
  }
  
  // Find discounts with pagination
  const { count, rows } = await Discount.findAndCountAll({
    where, // Where clause
    include: [
      {
        model: DiscountRule, // Include discount rules
        as: 'rules', // Use rules alias
        required: false, // Left join
      },
    ],
    limit, // Limit results
    offset, // Offset results
    order: [['created_at', 'DESC']], // Order by creation date descending
  });
  
  // Calculate pagination metadata
  const totalPages = Math.ceil(count / limit); // Total pages
  const hasNextPage = page < totalPages; // Has next page
  const hasPrevPage = page > 1; // Has previous page
  
  // Return paginated results
  return {
    discounts: rows, // Discounts array
    pagination: {
      page, // Current page
      limit, // Items per page
      total: count, // Total count
      totalPages, // Total pages
      hasNextPage, // Has next page
      hasPrevPage, // Has previous page
    },
  };
};

/**
 * Get discounts available for a product
 * Returns all active discounts that apply to a specific product based on rules
 * Supports PRODUCT, CATEGORY, and ALL rule types
 * @param {number} productId - Product ID
 * @returns {Promise<Array>} Array of applicable discounts
 */
const getDiscountsForProduct = async (productId) => {
  // Validate product ID
  if (!productId) {
    throw new ValidationError('Product ID is required');
  }

  // Get all active discounts
  const allDiscounts = await Discount.findAll({
    where: { active: true },
    include: [
      {
        model: DiscountRule,
        as: 'rules',
        required: false,
      },
    ],
    order: [['created_at', 'DESC']],
  });

  // Import discount calculation service to check rule applicability
  const { checkDiscountRuleForProduct } = require('./discountCalculation');

  // Filter discounts that apply to this product
  const applicableDiscounts = [];
  
  for (const discount of allDiscounts) {
    const rules = discount.rules || [];
    
    // If no rules, discount applies to all products (ALL)
    if (rules.length === 0) {
      applicableDiscounts.push(discount);
      continue;
    }

    // Check if any rule applies to this product
    let appliesToProduct = false;
    for (const rule of rules) {
      const applies = await checkDiscountRuleForProduct(rule, productId);
      if (applies) {
        appliesToProduct = true;
        break;
      }
    }

    if (appliesToProduct) {
      applicableDiscounts.push(discount);
    }
  }

  return applicableDiscounts;
};

// Export discount service functions
module.exports = {
  createDiscount,
  getDiscount,
  getDiscountByCode,
  updateDiscount,
  deleteDiscount,
  listDiscounts,
  getDiscountsForProduct,
};

