import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { SetStockDto } from './dto/set-stock.dto';
import { BulkSetStockDto } from './dto/bulk-set-stock.dto';
import { BulkReserveFoodDto } from './dto/bulk-reserve-food.dto';
import { Inventory } from './entities/inventory.entity';
import { Food } from '../foods/entities/food.entity';
import { Restaurant } from '../restaurants/entities/restaurant.entity';

@Injectable()
export class InventoryService {
  constructor(private readonly em: EntityManager) {}

  async setStockForFood(foodId: string, restaurantId: string, setStockDto: SetStockDto): Promise<Inventory> {
    // Validate that availableStock doesn't exceed totalStock
    if (setStockDto.availableStock > setStockDto.totalStock) {
      throw new BadRequestException('Available stock cannot exceed total stock');
    }

    // Find food and verify it belongs to the restaurant
    const food = await this.em.findOne(Food, { id: foodId }, { populate: ['restaurant'] });
    if (!food) {
      throw new NotFoundException(`Food with ID ${foodId} not found`);
    }

    if (food.restaurant.id !== restaurantId) {
      throw new BadRequestException(`Food does not belong to restaurant ${restaurantId}`);
    }

    // Find or create inventory record
    let inventory = await this.em.findOne(Inventory, {
      food: { id: foodId, restaurant: { id: restaurantId } },
    });

    if (!inventory) {
      // Create new inventory record
      const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
      if (!restaurant) {
        throw new NotFoundException(`Restaurant with ID ${restaurantId} not found`);
      }

      inventory = this.em.create(Inventory, {
        food,
        totalStock: setStockDto.totalStock,
        availableStock: setStockDto.availableStock,
      });
    } else {
      // Update existing inventory record
      inventory.totalStock = setStockDto.totalStock;
      inventory.availableStock = setStockDto.availableStock;
    }

    await this.em.flush();

    return inventory;
  }

  async bulkSetStockForFoods(restaurantId: string, bulkSetStockDto: BulkSetStockDto): Promise<Inventory[]> {
    const { items } = bulkSetStockDto;
    // Validate all items first
    for (const item of items) {
      if (item.availableStock > item.totalStock) {
        throw new BadRequestException(`Available stock cannot exceed total stock for food ${item.foodId}`);
      }
    }

    // Get all food IDs
    const foodIds = items.map(item => item.foodId);

    // Load all foods in one query
    const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['restaurant'] });

    // Verify all foods exist and belong to the restaurant
    const foodMap = new Map<string, Food>();
    for (const food of foods) {
      if (food.restaurant.id !== restaurantId) {
        throw new BadRequestException(`Food ${food.id} does not belong to restaurant ${restaurantId}`);
      }
      foodMap.set(food.id, food);
    }

    // Check for missing foods
    const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
    if (missingFoodIds.length > 0) {
      throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`);
    }

    // Load all existing inventories in one query
    const existingInventories = await this.em.find(Inventory, {
      food: { id: { $in: foodIds }, restaurant: { id: restaurantId } },
    });

    const inventoryMap = new Map<string, Inventory>();
    for (const inventory of existingInventories) {
      inventoryMap.set(inventory.food.id, inventory);
    }

    // Get restaurant once
    const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
    if (!restaurant) {
      throw new NotFoundException(`Restaurant with ID ${restaurantId} not found`);
    }

    // Process all items
    const results: Inventory[] = [];
    for (const item of items) {
      const food = foodMap.get(item.foodId)!;
      let inventory = inventoryMap.get(item.foodId);

      if (!inventory) {
        // Create new inventory record
        inventory = this.em.create(Inventory, {
          food,
          totalStock: item.totalStock,
          availableStock: item.availableStock,
        });
      } else {
        // Update existing inventory record
        inventory.totalStock = item.totalStock;
        inventory.availableStock = item.availableStock;
      }

      results.push(inventory);
    }

    // Flush all changes at once
    await this.em.flush();

    return results;
  }

  async deductFromInventory(em: EntityManager, bulkReserveFoodDto: BulkReserveFoodDto): Promise<Inventory[]> {
    return em.transactional(async em => {
      const { items } = bulkReserveFoodDto;
      // Get all unique food IDs
      const foodIds = [...new Set(items.map(item => item.foodId))];

      // Load all foods in one query
      const foods = await em.find(Food, { id: { $in: foodIds } });

      // Verify all foods exist and belong to the restaurant
      const foodMap = new Map<string, Food>();
      for (const food of foods) {
        foodMap.set(food.id, food);
      }

      // Check for missing foods
      const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
      if (missingFoodIds.length > 0) {
        throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`);
      }

      // Load all existing inventories in one query
      const existingInventories = await em.find(Inventory, {
        food: { id: { $in: foodIds } },
      });

      const inventoryMap = new Map<string, Inventory>();
      for (const inventory of existingInventories) {
        inventoryMap.set(inventory.food.id, inventory);
      }

      // Validate stock availability and create reservations
      const inventories: Inventory[] = [];
      for (const item of items) {
        const inventory = inventoryMap.get(item.foodId);

        // Check if inventory exists
        if (!inventory) {
          throw new NotFoundException(`Inventory not found for food ${item.foodId}`);
        }

        // Check if available stock is sufficient
        if (inventory.availableStock < item.quantity) {
          throw new BadRequestException(
            `Insufficient stock for food ${item.foodId}. Available: ${inventory.availableStock}, Requested: ${item.quantity}`,
          );
        }
        inventory.availableStock -= item.quantity;
        inventories.push(inventory);
        em.persist(inventory);
      }
      return inventories;
    });
  }

  async restoreToInventory(em: EntityManager, bulkRestoreDto: BulkReserveFoodDto): Promise<Inventory[]> {
    return em.transactional(async em => {
      const { items } = bulkRestoreDto;

      const foodIds = [...new Set(items.map(item => item.foodId))];

      const foods = await em.find(Food, { id: { $in: foodIds } });
      const foodMap = new Map<string, Food>();
      for (const food of foods) {
        foodMap.set(food.id, food);
      }

      const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
      if (missingFoodIds.length > 0) {
        throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`);
      }

      const existingInventories = await em.find(Inventory, {
        food: { id: { $in: foodIds } },
      });

      const inventoryMap = new Map<string, Inventory>();
      for (const inventory of existingInventories) {
        inventoryMap.set(inventory.food.id, inventory);
      }

      const inventories: Inventory[] = [];
      for (const item of items) {
        const inventory = inventoryMap.get(item.foodId);
        if (!inventory) {
          throw new NotFoundException(`Inventory not found for food ${item.foodId}`);
        }
        inventory.availableStock += item.quantity;
        inventories.push(inventory);
        em.persist(inventory);
      }

      return inventories;
    });
  }
}
