/**
 * Product Management Service
 * Business logic for product management operations
 */

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

/**
 * Create a new product
 * @param {Object} productData - Product data (name, sku, product_type, track_inventory, description, low_stock_threshold)
 * @returns {Promise<Object>} Created product
 * @throws {ValidationError} If validation fails
 * @throws {ConflictError} If SKU already exists
 */
const createProduct = async (productData) => {
  // Extract product data
  const { name, sku, product_type, track_inventory = true, description, low_stock_threshold, track_by_dimensions, unit_of_measure, selling_price } = productData;
  
  // Validate required fields
  if (!name || !product_type) {
    throw new ValidationError('Product name and type are required');
  }
  
  // Validate product_type
  const validTypes = ['FG', 'RM']; // Finished Goods, Raw Materials
  if (!validTypes.includes(product_type)) {
    throw new ValidationError(`Product type must be one of: ${validTypes.join(', ')}`);
  }
  
  // Check SKU uniqueness if provided
  if (sku) {
    const existingProduct = await Product.findOne({ where: { sku } });
    if (existingProduct) {
      throw new ConflictError('SKU already exists');
    }
  }
  
  // Determine sell_on_pos: FG products default to true, RM products default to false
  const sell_on_pos = productData.sell_on_pos !== undefined 
    ? productData.sell_on_pos 
    : (product_type === 'FG' ? true : false); // FG products sellable by default, RM not
  
  // Create product with error handling for Sequelize validation errors
  let product;
  try {
    product = await Product.create({
      name, // Product name
      sku, // SKU (optional but unique if provided)
      product_type, // Product type (FG or RM)
      track_inventory, // Track inventory flag
      description, // Product description (optional)
      low_stock_threshold, // Low stock threshold (optional)
      sell_on_pos, // Sell on POS flag (FG=true by default, RM=false by default)
      track_by_dimensions, // Track by dimensions flag (for dimension-based RM)
      unit_of_measure, // Unit of measure (inch, cm, m) for dimension-based RM
      selling_price, // Selling price (optional)
    });
  } catch (error) {
    // Handle Sequelize unique constraint errors (e.g., duplicate SKU)
    if (error.name === 'SequelizeUniqueConstraintError') {
      const field = error.errors?.[0]?.path || 'field';
      throw new ConflictError(`${field === 'sku' ? 'SKU' : field} already exists`);
    }
    
    // Handle Sequelize validation errors
    if (error.name === 'SequelizeValidationError') {
      // Format Sequelize validation errors
      const formattedErrors = error.errors ? error.errors.map((err) => ({
        field: err.path,
        message: err.message,
        value: err.value,
      })) : null;
      
      // Throw ValidationError with formatted details
      throw new ValidationError(
        error.message || 'Product validation failed',
        formattedErrors
      );
    }
    
    // Re-throw other errors
    throw error;
  }
  
  // Reload product with categories
  await product.reload({
    include: [
      {
        model: ProductCategory,
        as: 'productCategories',
        required: false,
        include: [
          {
            model: Category,
            as: 'category',
            attributes: ['id', 'name', 'slug'],
          },
        ],
      },
    ],
  });
  
  // Get product data as JSON
  const productJson = product.toJSON();
  
  // Transform productCategories to a simpler format
  if (productJson.productCategories) {
    productJson.categories = productJson.productCategories.map(pc => {
      // Ensure category name is always included
      const categoryName = pc.category?.name || null;
      const categoryId = pc.category_id || pc.category?.id || null;
      
      return {
        id: categoryId,
        name: categoryName, // This should always be present if category is included
        slug: pc.category?.slug || null,
        is_primary: pc.is_primary || false,
      };
    }).filter(cat => cat.id !== null); // Filter out null categories
  }
  
  // Return product data
  return productJson;
};

/**
 * Get product by ID
 * @param {number} productId - Product ID
 * @returns {Promise<Object>} Product object
 * @throws {NotFoundError} If product not found
 */
const getProductById = async (productId) => {
  // Build include options
  const includeOptions = [];
  
  // Always include product categories
  includeOptions.push({
    model: ProductCategory,
    as: 'productCategories',
    required: false, // LEFT JOIN - include products even without categories
    include: [
      {
        model: Category,
        as: 'category',
        attributes: ['id', 'name', 'slug'],
      },
    ],
  });
  
  // Find product by ID
  const product = await Product.findByPk(productId, {
    include: includeOptions,
  });
  
  // If product not found, throw not found error
  if (!product) {
    throw new NotFoundError('Product not found');
  }
  
  // Get product data as JSON
  const productData = product.toJSON();
  
  // Transform productCategories to a simpler format if needed
  if (productData.productCategories) {
    productData.categories = productData.productCategories.map(pc => {
      // Ensure category name is always included
      const categoryName = pc.category?.name || null;
      const categoryId = pc.category_id || pc.category?.id || null;
      
      // If category name is missing but we have category ID, we should fetch it
      // But for now, return what we have - the frontend will handle fetching if needed
      return {
        id: categoryId,
        name: categoryName, // This should always be present if category is included
        slug: pc.category?.slug || null,
        is_primary: pc.is_primary || false,
      };
    }).filter(cat => cat.id !== null); // Filter out null categories
  }
  
  // Return product data
  return productData;
};

/**
 * Update product
 * @param {number} productId - Product ID
 * @param {Object} updateData - Product data to update
 * @returns {Promise<Object>} Updated product object
 * @throws {NotFoundError} If product not found
 * @throws {ConflictError} If SKU already exists
 */
const updateProduct = async (productId, updateData) => {
  // Find product by ID
  const product = await Product.findByPk(productId);
  
  // If product not found, throw not found error
  if (!product) {
    throw new NotFoundError('Product not found');
  }
  
  // Extract update data
  const { name, sku, product_type, track_inventory, description, low_stock_threshold, selling_price, track_by_dimensions, unit_of_measure } = updateData;
  
  // Validate product_type if provided
  if (product_type) {
    const validTypes = ['FG', 'RM'];
    if (!validTypes.includes(product_type)) {
      throw new ValidationError(`Product type must be one of: ${validTypes.join(', ')}`);
    }
  }
  
  // Check SKU uniqueness if SKU is being changed
  if (sku && sku !== product.sku) {
    const existingProduct = await Product.findOne({ where: { sku } });
    if (existingProduct) {
      throw new ConflictError('SKU already exists');
    }
  }
  
  // Build update object (only include provided fields)
  const updateFields = {};
  if (name !== undefined) updateFields.name = name;
  if (sku !== undefined) updateFields.sku = sku;
  if (product_type !== undefined) updateFields.product_type = product_type;
  if (track_inventory !== undefined) updateFields.track_inventory = track_inventory;
  if (description !== undefined) updateFields.description = description;
  if (low_stock_threshold !== undefined) updateFields.low_stock_threshold = low_stock_threshold;
  if (selling_price !== undefined) updateFields.selling_price = selling_price;
  if (track_by_dimensions !== undefined) updateFields.track_by_dimensions = track_by_dimensions;
  if (unit_of_measure !== undefined) updateFields.unit_of_measure = unit_of_measure;
  
  // Update product
  await product.update(updateFields);
  
  // Reload product with categories
  await product.reload({
    include: [
      {
        model: ProductCategory,
        as: 'productCategories',
        required: false,
        include: [
          {
            model: Category,
            as: 'category',
            attributes: ['id', 'name', 'slug'],
          },
        ],
      },
    ],
  });
  
  // Get product data as JSON
  const productData = product.toJSON();
  
  // Transform productCategories to a simpler format if needed
  if (productData.productCategories) {
    productData.categories = productData.productCategories.map(pc => ({
      id: pc.category_id || pc.category?.id,
      name: pc.category?.name,
      slug: pc.category?.slug,
      is_primary: pc.is_primary,
    }));
  }
  
  // Return updated product data
  return productData;
};

/**
 * Delete product (soft delete by setting inactive flag)
 * Note: Products are not actually deleted for audit purposes
 * @param {number} productId - Product ID
 * @returns {Promise<Object>} Deleted product object
 * @throws {NotFoundError} If product not found
 */
const deleteProduct = async (productId) => {
  // Find product by ID
  const product = await Product.findByPk(productId);
  
  // If product not found, throw not found error
  if (!product) {
    throw new NotFoundError('Product not found');
  }
  
  // Note: Products are not deleted, but we can mark them as inactive if needed
  // For now, we'll throw an error to prevent accidental deletion
  // In a real system, you might want to check if product is used in sales/inventory
  throw new ValidationError('Product deletion is not allowed. Products are kept for audit purposes.');
  
  // If deletion is needed in the future, you could:
  // await product.update({ active: false });
  // return product.toJSON();
};

/**
 * List products with pagination and filters
 * @param {Object} options - Query options (page, limit, search, product_type, track_inventory, include, price_list_id, quantity)
 * @returns {Promise<Object>} Paginated products list
 */
const listProducts = async (options = {}) => {
  // Extract query options
  const { 
    page = 1, 
    limit = 10, 
    search, 
    product_type, 
    track_inventory, 
    category_id,
    ids = null, // NEW: Filter by product IDs (array of IDs)
    sell_on_pos, // NEW: Filter by sell_on_pos flag (for POS filtering)
    include = [], // NEW: Array of things to include (e.g., ['prices', 'inventory'])
    price_list_id = null, // NEW: Price list ID for pricing
    quantity = 1, // NEW: Quantity for quantity-based pricing
  } = options;
  
  // Build where clause for filtering
  const where = {};
  
  // IDs filter (filter by specific product IDs)
  if (ids && Array.isArray(ids) && ids.length > 0) {
    where.id = { [Op.in]: ids }; // Filter by IDs using IN operator
  }
  
  // Search filter (name or SKU)
  if (search) {
    where[Op.or] = [
      { name: { [Op.like]: `%${search}%` } }, // Search in name
      { sku: { [Op.like]: `%${search}%` } }, // Search in SKU
    ];
  }
  
  // Product type filter
  if (product_type) {
    where.product_type = product_type; // Filter by product type
  }
  
  // Track inventory filter
  if (track_inventory !== undefined) {
    where.track_inventory = track_inventory === 'true' || track_inventory === true; // Filter by track_inventory
  }
  
  // Sell on POS filter (important for POS - only show sellable products)
  if (sell_on_pos !== undefined) {
    where.sell_on_pos = sell_on_pos === 'true' || sell_on_pos === true; // Filter by sell_on_pos
  } else if (include.includes('prices') || include.includes('inventory')) {
    // If requesting prices/inventory (likely for POS), default to showing only sellable products
    // This ensures POS only shows products that can be sold
    where.sell_on_pos = true;
  }
  
  // For POS context: if prices/inventory are requested and no product_type specified,
  // default to Finished Goods (FG) only - Raw Materials (RM) should not appear on POS
  if ((include.includes('prices') || include.includes('inventory')) && !product_type) {
    where.product_type = 'FG'; // Default to Finished Goods for POS
  }
  
  // Build include options
  const includeOptions = [];
  
  // Include inventory if requested
  if (include.includes('inventory')) {
    includeOptions.push({
      model: Inventory,
      as: 'inventories', // Use plural alias as defined in Product model
      required: false, // LEFT JOIN - products without inventory still included
      // Don't filter by variant_id - include both product-level and variant-level inventory
      // This allows aggregation of variant inventories in the enrichment logic
    });
  }
  
  // Category filter - join with ProductCategory if category_id provided
  if (category_id) {
    includeOptions.push({
      model: ProductCategory,
      as: 'productCategories',
      where: { category_id: parseInt(category_id) },
      required: true, // INNER JOIN - only products in this category
    });
  }
  
  // Calculate offset for pagination
  const offset = (page - 1) * limit;
  
  // Find products with pagination
  const { count, rows } = await Product.findAndCountAll({
    where, // Filter conditions
    include: includeOptions,
    distinct: true, // Important: ensures correct count when using joins
    limit: parseInt(limit), // Number of records per page
    offset: parseInt(offset), // Skip records
    order: [['created_at', 'DESC']], // Order by creation date (newest first)
  });
  
  // Enrich products with prices and inventory if requested
  let enrichedProducts = rows.map(product => product.toJSON());
  
  if (include.includes('prices') || include.includes('inventory')) {
    const pricingService = require('../../pricing/services/productPrices');
    const stockCheckingService = require('../../inventory/services/stockChecking');
    
    enrichedProducts = await Promise.all(
      enrichedProducts.map(async (product) => {
        // Add price if requested
        if (include.includes('prices')) {
          try {
            const price = await pricingService.getPrice(
              product.id, 
              null, // variant_id (null - variants removed)
              parseFloat(quantity),
              price_list_id
            );
            
            if (price && price.price) {
              product.price = {
                amount: parseFloat(price.price),
                price_list_id: price.price_list_id,
                price_list_name: price.priceList?.name,
                price_list_code: price.priceList?.code,
                effective_from: price.effective_from,
                effective_to: price.effective_to,
                min_quantity: price.min_quantity,
                max_quantity: price.max_quantity,
              };
            } else {
              // Fallback to selling_price if no price list price
              product.price = product.selling_price ? {
                amount: parseFloat(product.selling_price),
                source: 'selling_price',
              } : null;
            }
          } catch (error) {
            // If pricing fails, fallback to selling_price
            product.price = product.selling_price ? {
              amount: parseFloat(product.selling_price),
              source: 'selling_price',
            } : null;
          }
        }
        
        // Add inventory if requested
        if (include.includes('inventory')) {
          try {
            // Check if product tracks inventory
            if (product.track_inventory) {
              // Always use stock checking service for accurate availability (includes reserved counts)
              // This service handles finding the inventory record and calculating available quantity
              const availability = await stockCheckingService.getProductAvailability(product.id, null);
              
              // Ensure we have valid numeric values (not NaN)
              const availableQty = parseFloat(availability.available_qty || 0);
              const reservedQty = parseFloat(availability.reserved_qty || 0);
              const totalQty = parseFloat(availability.total_qty || (availableQty + reservedQty));
              const reorderLevel = parseFloat(availability.reorder_level || 0);
              
              product.inventory = {
                available_qty: isNaN(availableQty) ? 0 : availableQty,
                reserved_qty: isNaN(reservedQty) ? 0 : reservedQty,
                total_qty: isNaN(totalQty) ? (availableQty + reservedQty) : totalQty,
                reorder_level: isNaN(reorderLevel) ? 0 : reorderLevel,
              };
            } else {
              // Product doesn't track inventory
              product.inventory = {
                available_qty: null, // Indicates not tracked
                reserved_qty: null,
                total_qty: null,
                reorder_level: null,
              };
            }
            
            // Remove the raw inventories array from response (we've processed it)
            if (product.inventories) {
              delete product.inventories;
            }
          } catch (error) {
            // Log error but don't fail the request
            logger.error(`Error fetching inventory for product ${product.id} (${product.name || product.sku || 'unknown'}):`, {
              error: error.message,
              stack: error.stack?.split('\n')[0], // Only first line of stack
            });
            
            // If inventory check fails, try to get from included data as fallback
            const inventoryRecords = product.inventories || [];
            const productInventory = inventoryRecords[0]; // Get first inventory record (no variants)
            
            if (productInventory && product.track_inventory) {
              // Use quantity from included inventory record as fallback
              const qty = parseFloat(productInventory.quantity || 0);
              const reorderLevel = parseFloat(productInventory.reorder_level || 0);
              
              product.inventory = {
                available_qty: isNaN(qty) ? 0 : qty,
                reserved_qty: 0,
                total_qty: isNaN(qty) ? 0 : qty,
                reorder_level: isNaN(reorderLevel) ? 0 : reorderLevel,
              };
              
              logger.info(`Used fallback inventory data for product ${product.id}: available_qty=${qty}`);
            } else {
              // No inventory data available
              product.inventory = {
                available_qty: product.track_inventory ? 0 : null,
                reserved_qty: null,
                total_qty: null,
                reorder_level: null,
              };
              
              if (product.track_inventory) {
                logger.warn(`No inventory record found for tracked product ${product.id} (${product.name || product.sku || 'unknown'})`);
              }
            }
            
            // Remove the raw inventories array from response
            if (product.inventories) {
              delete product.inventories;
            }
          }
        }
        
        return product;
      })
    );
  }
  
  // Calculate pagination metadata
  const totalPages = Math.ceil(count / limit); // Total number of pages
  
  // Return paginated results
  return {
    products: enrichedProducts, // Return enriched products
    pagination: {
      page: parseInt(page), // Current page
      limit: parseInt(limit), // Records per page
      total: count, // Total number of records
      totalPages, // Total number of pages
    },
  };
};

/**
 * Search products
 * @param {string} searchTerm - Search term (searches in name and SKU)
 * @param {Object} options - Additional options (limit)
 * @returns {Promise<Array>} Array of matching products
 */
const searchProducts = async (searchTerm, options = {}) => {
  // Extract options
  const { limit = 20 } = options;
  
  // If no search term, return empty array
  if (!searchTerm || searchTerm.trim().length === 0) {
    return [];
  }
  
  // Find products matching search term
  const products = await Product.findAll({
    where: {
      [Op.or]: [
        { name: { [Op.like]: `%${searchTerm}%` } }, // Search in name
        { sku: { [Op.like]: `%${searchTerm}%` } }, // Search in SKU
      ],
    },
    limit: parseInt(limit), // Limit results
    order: [['name', 'ASC']], // Order by name
  });
  
  // Return matching products
  return products.map(product => product.toJSON());
};

// Export service functions
module.exports = {
  createProduct, // Create product
  getProductById, // Get product by ID
  updateProduct, // Update product
  deleteProduct, // Delete product (not allowed)
  listProducts, // List products with pagination
  searchProducts, // Search products
};
