import type { MealPlanRepository } from './meal-plans.repository.js';
import type { RecipesRepository } from '../recipes/recipes.repository.js';
import type { PantryRepository } from '../pantry/pantry.repository.js';
import type { ProductsRepository } from '../products/products.repository.js';
import { NotFoundError } from '../../common/errors.js';

interface Deps {
  mealPlanRepository: MealPlanRepository;
  recipesRepository: RecipesRepository;
  pantryRepository: PantryRepository;
  productsRepository: ProductsRepository;
}

export interface GapItem {
  productId: string;
  productName: string;
  category: string;
  requiredQuantity: number;
  pantryQuantity: number;
  missingQuantity: number;
  unit: string;
}

export interface ShoppingGapResult {
  mealPlanId: string;
  missingItems: GapItem[];
}

export class ShoppingGapService {
  private readonly mealPlanRepository: MealPlanRepository;
  private readonly recipesRepository: RecipesRepository;
  private readonly pantryRepository: PantryRepository;
  private readonly productsRepository: ProductsRepository;

  public constructor({
    mealPlanRepository,
    recipesRepository,
    pantryRepository,
    productsRepository,
  }: Deps) {
    this.mealPlanRepository = mealPlanRepository;
    this.recipesRepository = recipesRepository;
    this.pantryRepository = pantryRepository;
    this.productsRepository = productsRepository;
  }

  public async calculateGap(householdId: string, mealPlanId: string): Promise<ShoppingGapResult> {
    const plan = await this.mealPlanRepository.findById(mealPlanId, householdId);
    if (!plan) {
      throw new NotFoundError('Meal plan not found');
    }

    // 1. Collect unique recipe IDs in the plan
    const recipeIdSet = new Set<string>();
    const plannedMealsList: Array<{ recipeId: string; plannedServings: number }> = [];

    const recPlan = plan as Record<string, unknown>;
    const days = (recPlan.days as any[]) || [];
    for (const day of days) {
      const meals = (day.meals as any[]) || [];
      for (const meal of meals) {
        if (meal.recipeId) {
          recipeIdSet.add(meal.recipeId);
          plannedMealsList.push({
            recipeId: meal.recipeId,
            plannedServings: meal.servings,
          });
        }
      }
    }

    // 2. Fetch all referenced recipes in parallel
    const recipeIds = Array.from(recipeIdSet);
    const recipeDocs = await Promise.all(
      recipeIds.map((id) => this.recipesRepository.findById(id, householdId)),
    );
    const recipesMap = new Map<string, any>();
    for (const doc of recipeDocs) {
      if (doc) {
        recipesMap.set((doc as any)._id.toString(), doc);
      }
    }

    // 3. Aggregate required quantities of each Product
    // Map of productId -> { qty, unit }
    const requiredMap = new Map<string, { qty: number; unit: string }>();

    for (const planned of plannedMealsList) {
      const recipe = recipesMap.get(planned.recipeId);
      if (!recipe) continue;

      const baseServings = recipe.servings || 1;
      const scalingFactor = planned.plannedServings / baseServings;

      const ingredients = (recipe.ingredients as any[]) || [];
      for (const ing of ingredients) {
        if (ing.isOptional) continue;

        const productId = ing.productId as string;
        const scaledQty = (ing.quantity as number) * scalingFactor;
        const unit = (ing.unit as string) || 'g';

        const existing = requiredMap.get(productId);
        if (existing) {
          existing.qty += scaledQty;
        } else {
          requiredMap.set(productId, { qty: scaledQty, unit });
        }
      }
    }

    // 4. Fetch active pantry inventory & relevant Product models
    const activePantry = await this.pantryRepository.findActiveByHousehold(householdId);
    const requiredProductIds = Array.from(requiredMap.keys());
    const productDocs = await this.productsRepository.findByIds(householdId, requiredProductIds);

    const productsInfoMap = new Map<string, any>();
    for (const p of productDocs) {
      productsInfoMap.set(p._id.toString(), p);
    }

    // Aggregate current Pantry items by productId
    const pantryMap = new Map<string, number>();
    for (const item of activePantry) {
      const rec = item as Record<string, unknown>;
      const prodId = rec.productId as string;
      const qty = (rec.quantity as number) || 0;
      pantryMap.set(prodId, (pantryMap.get(prodId) || 0) + qty);
    }

    // 5. Contrast requirements vs inventory to find missing gaps
    const missingItems: GapItem[] = [];

    for (const [productId, req] of requiredMap.entries()) {
      const pantryQty = pantryMap.get(productId) || 0;

      if (pantryQty < req.qty) {
        const productDoc = productsInfoMap.get(productId);
        const productName = productDoc?.name || 'Unknown Ingredient';
        const category = productDoc?.category || 'other';

        missingItems.push({
          productId,
          productName,
          category,
          requiredQuantity: Math.round(req.qty * 100) / 100,
          pantryQuantity: Math.round(pantryQty * 100) / 100,
          missingQuantity: Math.round((req.qty - pantryQty) * 100) / 100,
          unit: req.unit,
        });
      }
    }

    return {
      mealPlanId,
      missingItems: missingItems.sort((a, b) => a.productName.localeCompare(b.productName)),
    };
  }
}
