/**
 * Returns Service
 * Business logic for return processing and credit notes
 */

// Import Return and related models
const { Return, ReturnItem, Sale, SaleItem, InventoryItem, Inventory, InventoryMovement, FiscalReceipt, Product } = require('../../../models'); // ProductVariant removed
// Import inventory items service
const inventoryItemsService = require('../../inventory/services/items');
// Import inventory service
const inventoryService = require('../../inventory/services');
// Import custom error classes
const { NotFoundError, ValidationError } = require('../../../utils/errors');
// Import logger for logging
const logger = require('../../../utils/logger');
// Import Sequelize operators and transaction
const { Op } = require('sequelize');
const { sequelize } = require('../../../models');

/**
 * Calculate return amount
 * Calculates the total amount for a return item based on original sale item price
 * @param {Object} saleItem - Original sale item
 * @param {number} returnQuantity - Quantity being returned
 * @returns {Object} Calculated return amounts
 */
const calculateReturnItemAmount = (saleItem, returnQuantity) => {
  // Get unit price from original sale item (tax removed)
  const unitPrice = parseFloat(saleItem.unit_price); // Unit price from sale
  
  // Calculate line total (quantity * unit price)
  const lineTotal = parseFloat(returnQuantity) * unitPrice; // Line total
  
  // Tax removed - no VAT/tax calculations
  // Calculate item total (same as line total since no tax)
  const itemTotal = lineTotal; // Item total (no tax)
  
  // Return calculated amounts
  return {
    lineTotal: parseFloat(lineTotal.toFixed(2)), // Line total rounded to 2 decimals
    vatAmount: 0, // Tax removed - always 0
    itemTotal: parseFloat(itemTotal.toFixed(2)), // Item total rounded to 2 decimals
  };
};

/**
 * Create return
 * Creates a new return with items (DRAFT status)
 * @param {Object} returnData - Return data (sale_id, items, reason)
 * @param {number} userId - User ID creating the return
 * @returns {Promise<Object>} Created return with items
 */
const createReturn = async (returnData, userId) => {
  // Extract return data
  const { sale_id, items = [], reason = null } = returnData; // Extract data
  
  // Validate required fields
  if (!sale_id) {
    throw new ValidationError('Sale ID is required'); // Throw error if sale_id missing
  }
  
  // Validate items
  if (!items || items.length === 0) {
    throw new ValidationError('Return must have at least one item'); // Throw error if no items
  }
  
  // Validate sale exists
  const sale = await Sale.findByPk(sale_id, {
    include: [
      {
        model: SaleItem, // Include sale items
        as: 'items', // Use items alias
        include: [
          {
            model: InventoryItem, // Include inventory item
            as: 'inventoryItem', // Use inventoryItem alias
            required: false, // Left join (inventory item may be null)
          },
        ],
      },
    ],
  }); // Find sale with items
  
  // Check if sale exists
  if (!sale) {
    throw new NotFoundError(`Sale with ID ${sale_id} not found`); // Throw error if sale not found
  }
  
  // Start database transaction
  const transaction = await sequelize.transaction(); // Start transaction
  
  try {
    // Validate return items
    const validatedItems = []; // Array to store validated items
    
    for (const item of items) {
      const { sale_item_id, inventory_item_id = null, quantity, reason: itemReason = null } = item; // Extract item data (inventory_item_id optional)
      
      // Validate required fields
      if (!sale_item_id || !quantity) {
        throw new ValidationError('Return item must have sale_item_id and quantity'); // Throw error if missing required fields
      }
      
      // Validate quantity
      if (parseFloat(quantity) <= 0) {
        throw new ValidationError('Return quantity must be greater than 0'); // Throw error if invalid quantity
      }
      
      // Find sale item
      const saleItem = sale.items.find(si => si.id === sale_item_id); // Find sale item
      if (!saleItem) {
        throw new NotFoundError(`Sale item with ID ${sale_item_id} not found in sale ${sale_id}`); // Throw error if sale item not found
      }
      
      // Determine if this is a UID-based or quantity-based return
      const isUidBased = inventory_item_id !== null && inventory_item_id !== undefined; // Check if inventory_item_id provided
      let inventoryItem = null; // Initialize inventory item as null
      
      // For UID-based returns, validate inventory item
      if (isUidBased) {
        // Validate inventory item belongs to the sale item (if sale item has inventory_item_id)
        if (saleItem.inventory_item_id) {
          if (saleItem.inventory_item_id !== inventory_item_id) {
            throw new ValidationError(`Inventory item ${inventory_item_id} does not belong to sale item ${sale_item_id}`); // Throw error if mismatch
          }
        }
        
        // Get inventory item
        inventoryItem = await InventoryItem.findByPk(inventory_item_id, { transaction }); // Find inventory item
        if (!inventoryItem) {
          throw new NotFoundError(`Inventory item with ID ${inventory_item_id} not found`); // Throw error if inventory item not found
        }
        
        // Validate inventory item status is SOLD
        if (inventoryItem.status !== 'SOLD') {
          throw new ValidationError(`Inventory item ${inventory_item_id} status is ${inventoryItem.status}, must be SOLD`); // Throw error if not SOLD
        }
        
        // Validate inventory item belongs to the sale item's product
        if (inventoryItem.product_id !== saleItem.product_id) {
          throw new ValidationError(`Inventory item ${inventory_item_id} product does not match sale item ${sale_item_id}`); // Throw error if product mismatch
        }
      } else {
        // For quantity-based returns, validate that sale item doesn't have inventory_item_id
        if (saleItem.inventory_item_id) {
          throw new ValidationError(`Sale item ${sale_item_id} is UID-tracked. inventory_item_id is required for returns.`); // Throw error if UID-tracked sale but no inventory_item_id provided
        }
        
        // Validate return quantity doesn't exceed sale quantity
        if (parseFloat(quantity) > parseFloat(saleItem.quantity)) {
          throw new ValidationError(`Return quantity ${quantity} exceeds sale quantity ${saleItem.quantity} for sale item ${sale_item_id}`); // Throw error if quantity exceeds
        }
      }
      
      // Calculate return item amount
      const returnAmounts = calculateReturnItemAmount(saleItem, quantity); // Calculate amounts
      
      // Store validated item
      validatedItems.push({
        sale_item_id, // Sale item ID
        inventory_item_id, // Inventory item ID (null for quantity-based)
        quantity: parseFloat(quantity), // Quantity
        reason: itemReason, // Item reason
        returnAmounts, // Calculated amounts
        saleItem, // Original sale item
        inventoryItem, // Inventory item (null for quantity-based)
      }); // Add to validated items
    }
    
    // Calculate total return amount
    const totalAmount = validatedItems.reduce((sum, item) => sum + item.returnAmounts.itemTotal, 0); // Sum item totals
    
    // Create return
    const returnRecord = await Return.create({
      sale_id, // Sale ID
      status: 'DRAFT', // Status is DRAFT
      total_amount: parseFloat(totalAmount.toFixed(2)), // Total amount
      reason, // Return reason
      returned_at: new Date(), // Return date
    }, { transaction }); // Create return in transaction
    
    // Create return items
    const returnItems = []; // Array to store created return items
    for (const validatedItem of validatedItems) {
      // Build return item data (conditionally include inventory_item_id)
      const returnItemData = {
        return_id: returnRecord.id, // Return ID
        sale_item_id: validatedItem.sale_item_id, // Sale item ID
        quantity: validatedItem.quantity, // Quantity
        reason: validatedItem.reason, // Item reason
      };
      
      // Only include inventory_item_id if it's provided (UID-tracked items)
      if (validatedItem.inventory_item_id !== null && validatedItem.inventory_item_id !== undefined) {
        returnItemData.inventory_item_id = validatedItem.inventory_item_id; // Inventory item ID (only for UID-tracked)
      }
      
      // Create return item
      const returnItem = await ReturnItem.create(returnItemData, { transaction }); // Create return item in transaction
      
      returnItems.push(returnItem); // Add to array
    }
    
    // Commit transaction
    await transaction.commit(); // Commit transaction
    
    logger.info(`Return created: ${returnRecord.id} for sale ${sale_id} with ${returnItems.length} items`); // Log return creation
    
    // Return created return with items and associations
    return await getReturn(returnRecord.id); // Return return with associations
  } catch (error) {
    // Rollback transaction on error
    await transaction.rollback(); // Rollback transaction
    throw error; // Re-throw error
  }
};

/**
 * Get return by ID
 * Retrieves a return by ID with items
 * @param {number} returnId - Return ID
 * @returns {Promise<Object>} Return with items
 */
const getReturn = async (returnId) => {
  // Validate return ID
  if (!returnId) {
    throw new ValidationError('Return ID is required'); // Throw error if ID missing
  }
  
  // Find return with associations
  const returnRecord = await Return.findByPk(returnId, {
    include: [
      {
        model: Sale, // Include sale
        as: 'sale', // Use sale alias
        include: [
          {
            model: SaleItem, // Include sale items
            as: 'items', // Use items alias
          },
        ],
      },
      {
        model: ReturnItem, // Include return items
        as: 'items', // Use items alias
        include: [
          {
            model: SaleItem, // Include sale item
            as: 'saleItem', // Use saleItem alias
            required: false, // Optional join (in case sale item doesn't exist)
            include: [
              {
                model: Product, // Include product
                as: 'product', // Use product alias
                required: false, // Optional join (in case product doesn't exist)
              },
            ],
          },
          {
            model: InventoryItem, // Include inventory item
            as: 'inventoryItem', // Use inventoryItem alias
            required: false, // Optional join (null for quantity-based returns)
            include: [
              {
                model: Product, // Include product
                as: 'product', // Use product alias
              },
            ],
          },
        ],
      },
      {
        model: FiscalReceipt, // Include credit notes
        as: 'creditNotes', // Use creditNotes alias
        required: false, // Left join (credit notes may not exist yet)
      },
      {
        model: Product, // Include replacement product
        as: 'replacementProduct', // Use replacementProduct alias
        required: false, // Left join (replacement product may not exist)
      },
    ],
  });
  
  // Check if return exists
  if (!returnRecord) {
    throw new NotFoundError(`Return with ID ${returnId} not found`); // Throw error if not found
  }
  
  // Return return
  return returnRecord;
};

/**
 * List returns
 * Lists returns with optional filters
 * @param {Object} filters - Filter options (sale_id, status)
 * @param {Object} pagination - Pagination options (page, limit)
 * @returns {Promise<Object>} Paginated list of returns
 */
const listReturns = async (filters = {}, pagination = {}) => {
  // Extract filters
  const {
    sale_id, // Sale ID filter
    status, // Status filter
    start_date, // Start date filter
    end_date, // End date filter
  } = 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 sale_id filter
  if (sale_id) {
    where.sale_id = sale_id; // Filter by sale ID
  }
  
  // Add status filter
  if (status) {
    where.status = status; // Filter by status
  }
  
  // Add date range filter
  if (start_date || end_date) {
    where.created_at = {}; // Initialize date filter
    if (start_date) {
      where.created_at[Op.gte] = new Date(start_date); // Greater than or equal to start date
    }
    if (end_date) {
      const endDateObj = new Date(end_date); // Create end date
      endDateObj.setHours(23, 59, 59, 999); // Set to end of day
      where.created_at[Op.lte] = endDateObj; // Less than or equal to end date
    }
  }
  
  // Find returns with pagination
  const { count, rows } = await Return.findAndCountAll({
    where, // Where clause
    include: [
      {
        model: Sale, // Include sale
        as: 'sale', // Use sale alias
      },
    ],
    limit, // Limit results
    offset, // Offset results
    order: [['returned_at', 'DESC']], // Order by return 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 {
    returns: rows, // Returns array
    pagination: {
      page, // Current page
      limit, // Items per page
      total: count, // Total count
      totalPages, // Total pages
      hasNextPage, // Has next page
      hasPrevPage, // Has previous page
    },
  };
};

/**
 * Complete return
 * Completes a return by updating inventory, creating movements, and generating credit note
 * @param {number} returnId - Return ID
 * @param {number} userId - User ID completing the return
 * @param {Object} refundData - Refund information (refund_method, refund_amount, replacement_product_id, refund_reference)
 * @returns {Promise<Object>} Completed return with credit note
 */
const completeReturn = async (returnId, userId, refundData = {}) => {
  // Validate return ID
  if (!returnId) {
    throw new ValidationError('Return ID is required'); // Throw error if ID missing
  }
  
  // Get return with items
  const returnRecord = await getReturn(returnId); // Get return
  
  // Check if return is already completed
  if (returnRecord.status === 'COMPLETED') {
    throw new ValidationError('Return is already completed'); // Throw error if already completed
  }
  
  // Start database transaction
  const transaction = await sequelize.transaction(); // Start transaction
  
  try {
    // Process each return item
    for (const returnItem of returnRecord.items) {
      // Check if this is a UID-based or quantity-based return
      const isUidBased = returnItem.inventory_item_id !== null; // Check if inventory_item_id is present
      
      if (isUidBased) {
        // UID-based return: Update inventory item status
        const inventoryItem = returnItem.inventoryItem; // Get inventory item
        
        if (!inventoryItem) {
          throw new NotFoundError(`Inventory item with ID ${returnItem.inventory_item_id} not found`); // Throw error if inventory item not found
        }
        
        // Update inventory item status to RETURNED
        inventoryItem.status = 'RETURNED'; // Set status to RETURNED
        await inventoryItem.save({ transaction }); // Save inventory item in transaction
        
        // Get or create quantity-based inventory record
        const [inventory] = await Inventory.findOrCreate({
          where: {
            product_id: inventoryItem.product_id, // Match product ID
             // Match variant ID
          },
          defaults: {
            quantity: 0, // Default quantity
            reorder_level: 0, // Default reorder level
          },
          transaction, // Use transaction
        }); // Get or create inventory
        
        // Increment quantity in quantity-based inventory (by 1 for UID-based)
        await inventory.increment('quantity', { by: 1, transaction }); // Increment quantity
        await inventory.reload({ transaction }); // Reload to get updated quantity
        
        // Create inventory movement for return (SALE_RETURN) - UID-based
        await InventoryMovement.create({
          product_id: inventoryItem.product_id, // Product ID
           // Variant ID
          inventory_item_id: inventoryItem.id, // Inventory item ID
          quantity_change: 1, // Quantity change (+1 for UID-based)
          reason: 'SALE_RETURN', // Reason: SALE_RETURN
          reference_id: returnId, // Reference ID: return ID
        }, { transaction }); // Create movement in transaction
      } else {
        // Quantity-based return: Update inventory quantity directly
        // Get sale item to determine product/variant
        const saleItem = await SaleItem.findByPk(returnItem.sale_item_id, { transaction }); // Find sale item
        if (!saleItem) {
          throw new NotFoundError(`Sale item with ID ${returnItem.sale_item_id} not found`); // Throw error if sale item not found
        }
        
        // Get or create quantity-based inventory record
        const [inventory] = await Inventory.findOrCreate({
          where: {
            product_id: saleItem.product_id, // Match product ID
            // variant_id removed - variants no longer exist
          },
          defaults: {
            quantity: 0, // Default quantity
            reorder_level: 0, // Default reorder level
          },
          transaction, // Use transaction
        }); // Get or create inventory
        
        // Increment quantity in quantity-based inventory (by return quantity)
        const quantityChange = parseFloat(returnItem.quantity); // Get return quantity
        await inventory.increment('quantity', { by: quantityChange, transaction }); // Increment quantity
        await inventory.reload({ transaction }); // Reload to get updated quantity
        
        // Create inventory movement for return (SALE_RETURN) - quantity-based
        await InventoryMovement.create({
          product_id: saleItem.product_id, // Product ID
          // variant_id removed - variants no longer exist
          inventory_item_id: null, // No inventory item for quantity-based
          quantity_change: quantityChange, // Quantity change (positive for return)
          reason: 'SALE_RETURN', // Reason: SALE_RETURN
          reference_id: returnId, // Reference ID: return ID
        }, { transaction }); // Create movement in transaction
      }
    }
    
    // Generate credit note (FiscalReceipt with receipt_category='CREDIT_NOTE')
    const creditNote = await FiscalReceipt.create({
      sale_id: null, // No sale ID for credit notes
      return_id: returnId, // Link to return
      receipt_category: 'CREDIT_NOTE', // Receipt category: CREDIT_NOTE
      receipt_type: 'B2C', // Receipt type: B2C (Business to Consumer) for returns/credit notes
      customer_pin: null, // Customer PIN (customers removed from system)
      fiscal_status: 'PENDING', // Fiscal status: PENDING (to be fiscalised)
    }, { transaction }); // Create credit note in transaction
    
    // Extract refund information with defaults
    const refund_method = refundData?.refund_method || 'CREDIT_NOTE'; // Default to CREDIT_NOTE if not specified
    const refund_amount = refundData?.refund_amount !== undefined ? refundData.refund_amount : returnRecord.total_amount; // Default to total_amount if not specified
    const replacement_product_id = refundData?.replacement_product_id || null; // No replacement by default
    const refund_reference = refundData?.refund_reference || null; // No reference by default
    
    // Update return with refund information and status to COMPLETED
    returnRecord.status = 'COMPLETED'; // Set status to COMPLETED
    returnRecord.refund_method = refund_method; // Set refund method
    returnRecord.refund_amount = parseFloat(refund_amount); // Set refund amount
    returnRecord.refunded_at = new Date(); // Set refund date to now
    returnRecord.replacement_product_id = replacement_product_id; // Set replacement product (if any)
    returnRecord.refund_reference = refund_reference; // Set refund reference (if any)
    await returnRecord.save({ transaction }); // Save return in transaction
    
    // Commit transaction
    await transaction.commit(); // Commit transaction
    
    logger.info(`Return completed: ${returnId} - Updated inventory, created credit note ${creditNote.id}`); // Log return completion
    
    // Return completed return with credit note
    return await getReturn(returnId); // Return return with associations
  } catch (error) {
    // Rollback transaction on error
    await transaction.rollback(); // Rollback transaction
    throw error; // Re-throw error
  }
};

// Export return service functions
module.exports = {
  createReturn,
  getReturn,
  listReturns,
  completeReturn,
};
