import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateFoodDto } from '../dto/create-food.dto';
import { FindFoodsDto } from '../dto/find-foods.dto';
import { FoodRepository } from '../repositories/food.repository';
import { CategoryRepository } from '../repositories/category.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData, FilterQuery } from '@mikro-orm/core';
import { Food } from '../entities/food.entity';
import { CategoryMessage, FoodMessage, RestMessage } from 'src/common/enums/message.enum';
import { Favorite } from '../entities/favorite.entity';
import { MealType } from '../interface/food.interface';
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
import { RestaurantsService } from 'src/modules/restaurants/providers/restaurants.service';
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';

@Injectable()
export class FoodService {
  constructor(
    private readonly foodRepository: FoodRepository,
    private readonly categoryRepository: CategoryRepository,
    private readonly restService: RestaurantsService,
    private readonly em: EntityManager,
  ) { }

  async create(restId: string, createFoodDto: CreateFoodDto) {
    const { categoryId, dailyStock = 0, ...rest } = createFoodDto;
    const restaurant = await this.restService.findOneOrFail(restId);

    const category = await this.categoryRepository.findOne({ id: categoryId, restaurant: { id: restId } });
    if (!category) {
      throw new NotFoundException(CategoryMessage.NOT_FOUND);
    }

    const { food, inventory } = await this.em.transactional(async em => {
      // prepare data with defaults for required fields so repository.create typing is satisfied
      const data: RequiredEntityData<Food> = {
        desc: rest.desc,
        isActive: rest.isActive ?? true,
        inPlaceServe: rest.inPlaceServe ?? false,
        pickupServe: rest.pickupServe ?? false,
        discount: rest.discount ?? 0,
        isSpecialOffer: rest.isSpecialOffer ?? false,
        order: rest.order ?? null,
        // map single-title/content DTO to localized fields
        title: rest.title,
        content: rest.content,
        // numeric/array fields
        price: rest.price ?? 0,
        prepareTime: rest.prepareTime ?? 0,
        images: rest.images ?? undefined,
        // new fields
        weekDays: rest.weekDays ?? [0, 1, 2, 3, 4, 5, 6],
        mealTypes: rest.mealTypes ?? [],
        restaurant: restaurant,
        category: category,
      };

      const food = em.create(Food, data);
      const newInventoryRecord = em.create(Inventory, {
        food: food,
        availableStock: dailyStock,
        totalStock: dailyStock,
      });

      await em.flush();
      return { food, inventory: newInventoryRecord };
    });

    // Re-load created entities with the primary EM to ensure they're attached
    const savedFood = food?.id
      ? await this.foodRepository.findOne({ id: food.id }, { populate: ['category', 'restaurant'] })
      : null;
    const savedInventory = inventory?.id ? await this.em.findOne(Inventory, { id: inventory.id }) : inventory;

    return { food: savedFood ?? food, inventory: savedInventory ?? inventory };
  }

  findAll(restId: string, dto: FindFoodsDto) {
    return this.foodRepository.findAllPaginated(restId, dto);
  }

  /**
   * Public food detail (only active foods are visible).
   */
  async findByIdPublic(foodId: string, userId?: string): Promise<any> {
    const food = await this.foodRepository.findOne({ id: foodId, isActive: true }, { populate: ['category', 'inventory'] });
    if (!food) throw new NotFoundException(FoodMessage.NOT_FOUND);
    let isFavorite = false;
    if (userId) {
      isFavorite = (await this.em.count(Favorite, { user: { id: userId }, food: { id: foodId } })) > 0;
    }
    return {
      ...food,
      isFavorite,
    };
  }

  /**
   * Admin food detail (scoped to the authenticated restaurant).
   */
  async findAdminById(restId: string, id: string): Promise<Food> {
    const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } },
      { populate: ['category', 'inventory'] });

    if (!food) throw new NotFoundException(FoodMessage.NOT_FOUND);
    return food;
  }

  /**
   * Find active foods for a restaurant based on current week day and meal type in Iran timezone.
   * @param slug - Restaurant slug identifier
   * @returns Array of active foods matching current day and meal time
   */
  async findByRestuarantSlug(slug: string) {
    const restaurant = await this.restService.findBySlug(slug);

    const queryFilter: FilterQuery<Food> = {
      restaurant: restaurant.id,
      isActive: true,
    };

    return this.foodRepository.find(queryFilter, {
      fields: ['id', 'title', 'price', 'content', 'desc', 'images', 'category',
        'inPlaceServe', 'pickupServe', 'discount', 'isSpecialOffer'],
      orderBy: { order: 'asc' }
    });
  }

  // async findByWeekDateAndMealType(slug: string): Promise<Food[]> {
  //   const { iranWeekDay, mealType } = this.getCurrentIranTimeContext();

  //   const queryFilter: FilterQuery<Food> = {
  //     restaurant: { slug },
  //     isActive: true,
  //     weekDays: { $contains: iranWeekDay },
  //     ...(mealType ? { mealTypes: { $contains: mealType } } : {}),
  //   } as unknown as FilterQuery<Food>;

  //   return this.foodRepository.find(queryFilter, {
  //     populate: ['category'],
  //     orderBy: { order: 'asc' }
  //   });
  // }
  /**
   * Get current Iran timezone context (weekday and meal type).
   * @returns Object containing Iran weekday (0-6, where 0=Saturday) and current meal type
   */
  // private getCurrentIranTimeContext(): { iranWeekDay: number; mealType: MealType | null } {
  //   const nowInIran = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Tehran' }));
  //   const weekDay = nowInIran.getDay(); // 0 = Sunday, 6 = Saturday
  //   const currentHour = nowInIran.getHours();

  //   // Convert to Iran weekday: Saturday=0, Sunday=1, ..., Friday=6
  //   // JavaScript: Sunday=0, Monday=1, ..., Saturday=6
  //   // Iran week: Saturday=0, Sunday=1, ..., Friday=6
  //   const iranWeekDay = (weekDay + 1) % 7;

  //   const mealType = this.getMealTypeByHour(currentHour);

  //   return { iranWeekDay, mealType };
  // }

  /**
   * Determine meal type based on current hour in Iran timezone.
   * @param hour - Current hour (0-23)
   * @returns Meal type or null if outside meal hours
   */
  private getMealTypeByHour(hour: number): MealType | null {
    const MEAL_TIME_RANGES = {
      BREAKFAST: { start: 6, end: 11 },
      LUNCH: { start: 11, end: 15 },
      SNACK: { start: 15, end: 19 },
      DINNER: { start: 19, end: 24 },
    } as const;

    if (hour >= MEAL_TIME_RANGES.BREAKFAST.start && hour < MEAL_TIME_RANGES.BREAKFAST.end) {
      return MealType.BREAKFAST;
    }
    if (hour >= MEAL_TIME_RANGES.LUNCH.start && hour < MEAL_TIME_RANGES.LUNCH.end) {
      return MealType.LUNCH;
    }
    if (hour >= MEAL_TIME_RANGES.SNACK.start && hour < MEAL_TIME_RANGES.SNACK.end) {
      return MealType.SNACK;
    }
    if (hour >= MEAL_TIME_RANGES.DINNER.start && hour < MEAL_TIME_RANGES.DINNER.end) {
      return MealType.DINNER;
    }

    return null;
  }

  async update(restId: string, id: string, dto: Partial<CreateFoodDto>): Promise<Food> {
    const { categoryId, dailyStock, ...rest } = dto;
    const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
    if (!food) {
      throw new NotFoundException(FoodMessage.NOT_FOUND);
    }

    // attach new categories if provided (adds, does not attempt to preserve/remove existing ones)
    if (categoryId) {
      const category = await this.categoryRepository.findOne({ id: categoryId, restaurant: { id: restId } });

      if (!category) {
        throw new NotFoundException(CategoryMessage.NOT_FOUND);
      }

      this.em.assign(food, { category: category });
    }

    // assign other fields from DTO (excluding dailyStock handled below)
    this.em.assign(food, rest);

    // Persist food and update/create related inventory atomically
    await this.em.transactional(async em => {
      await em.persistAndFlush(food);

      if (typeof dailyStock !== 'undefined') {
        const inventoryRecord = await em.findOne(Inventory, { food: food });
        if (inventoryRecord) {
          inventoryRecord.totalStock = dailyStock;
        } else {
          em.create(Inventory, {
            food: food,
            availableStock: dailyStock,
            totalStock: dailyStock,
          });
        }
      }

      await em.flush();
    });

    // Re-load the food to ensure populated relations and stable instance
    const savedFood = await this.foodRepository.findOne({ id: food.id }, { populate: ['category', 'restaurant'] });

    // Invalidate cache for the restaurant if present (optional)
    // await this.invalidateRestaurantFoodsCache(savedFood?.restaurant?.slug);

    return savedFood ?? food;
  }

  async remove(restId: string, id: string): Promise<void> {
    const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
    if (!food) {
      throw new NotFoundException(FoodMessage.NOT_FOUND);
    }

    // const restaurantSlug = food.restaurant.slug;
    food.deletedAt = new Date();
    await this.em.persistAndFlush(food);

    // Invalidate cache for the restaurant
    // await this.invalidateRestaurantFoodsCache(restaurantSlug);
  }


  async toggleFavorite(userId: string, foodId: string) {

    const favorite = await this.em.findOne(Favorite, { user: userId, food: foodId });
    if (favorite) {
      return this.em.removeAndFlush(favorite);
    }
    const newFavorite = this.em.create(Favorite, {
      user: userId,
      food: foodId,
    });
    return this.em.persistAndFlush(newFavorite);
  }


  async getMyFavorites(userId: string, restId: string) {
    return this.em.find(Favorite, { user: userId, food: { restaurant: { id: restId } } }, { populate: ['food'] });
  }


}
