import type { MealPlanRepository } from './meal-plans.repository.js';
import { NotFoundError, BadRequestError } from '../../common/errors.js';
import type {
  CreateMealPlanInput,
  UpdateMealPlanInput,
  MealPlanQueryInput,
  MealPlanStatus,
  NutritionInfo,
  MealPlanDaySchema,
} from '@meshitrack/shared';
import { type z } from 'zod/v4';

type MealPlanDay = z.infer<typeof MealPlanDaySchema>;

interface Deps {
  mealPlanRepository: MealPlanRepository;
}

export class MealPlanService {
  private readonly mealPlanRepository: MealPlanRepository;

  public constructor({ mealPlanRepository }: Deps) {
    this.mealPlanRepository = mealPlanRepository;
  }

  public async list(householdId: string, query: MealPlanQueryInput) {
    return this.mealPlanRepository.findByHousehold(householdId, query);
  }

  public async getById(id: string, householdId: string) {
    const plan = await this.mealPlanRepository.findById(id, householdId);
    if (!plan) {
      throw new NotFoundError('Meal plan not found');
    }
    return plan;
  }

  public async getByWeek(householdId: string, weekStartDate: string) {
    return this.mealPlanRepository.findByWeek(householdId, weekStartDate);
  }

  public async create(householdId: string, createdBy: string, input: CreateMealPlanInput) {
    // Prevent overlapping meal plans for same household/week
    const existing = await this.mealPlanRepository.findByWeek(householdId, input.weekStartDate);
    if (existing) {
      throw new BadRequestError(
        `A meal plan already exists for household ${householdId} starting ${input.weekStartDate}`,
      );
    }

    // Force re-calculation of daily totals to ensure correctness
    const updatedDays = input.days.map((day) => this.computeDayTotals(day));

    return this.mealPlanRepository.create({
      ...input,
      days: updatedDays,
      householdId,
      createdBy,
    });
  }

  public async update(id: string, householdId: string, input: UpdateMealPlanInput) {
    const existing = await this.getById(id, householdId);

    const data: Record<string, unknown> = {};

    if (input.status !== undefined) data.status = input.status;
    if (input.shoppingListId !== undefined) data.shoppingListId = input.shoppingListId;

    if (input.days !== undefined) {
      // Force re-calculation of daily totals
      data.days = input.days.map((day) => this.computeDayTotals(day));
    }

    const updated = await this.mealPlanRepository.update(id, householdId, data);
    if (!updated) {
      throw new NotFoundError('Meal plan not found');
    }
    return updated;
  }

  public async updateStatus(id: string, householdId: string, status: MealPlanStatus) {
    await this.getById(id, householdId);
    const updated = await this.mealPlanRepository.updateStatus(id, householdId, status);
    if (!updated) {
      throw new NotFoundError('Meal plan not found');
    }
    return updated;
  }

  public async delete(id: string, householdId: string) {
    await this.getById(id, householdId);
    const deleted = await this.mealPlanRepository.delete(id, householdId);
    if (!deleted) {
      throw new NotFoundError('Meal plan not found');
    }
    return deleted;
  }

  /** Calculates standard daily totals based on the component meals. */
  private computeDayTotals(day: MealPlanDay): MealPlanDay {
    const total = {
      calories: 0,
      protein: 0,
      carbs: 0,
      fat: 0,
      fiber: 0,
      sugar: 0,
      sodium: 0,
      saturatedFat: 0,
      cholesterol: 0,
    };

    for (const meal of day.meals) {
      const servings = meal.servings;
      // Use customNutrition if provided, otherwise scale perServingNutrition
      const source = meal.customNutrition ?? meal.perServingNutrition;

      total.calories += source.calories * servings;
      total.protein += source.protein * servings;
      total.carbs += source.carbs * servings;
      total.fat += source.fat * servings;

      if (source.fiber != null) total.fiber += source.fiber * servings;
      if (source.sugar != null) total.sugar += source.sugar * servings;
      if (source.sodium != null) total.sodium += source.sodium * servings;
      if (source.saturatedFat != null) {
        total.saturatedFat += source.saturatedFat * servings;
      }
      if (source.cholesterol != null) {
        total.cholesterol += source.cholesterol * servings;
      }
    }

    // Round final totals to 2 decimal places
    return {
      ...day,
      dailyNutritionTotal: {
        calories: Math.round(total.calories * 100) / 100,
        protein: Math.round(total.protein * 100) / 100,
        carbs: Math.round(total.carbs * 100) / 100,
        fat: Math.round(total.fat * 100) / 100,
        fiber: Math.round(total.fiber * 100) / 100,
        sugar: Math.round(total.sugar * 100) / 100,
        sodium: Math.round(total.sodium * 100) / 100,
        saturatedFat: Math.round(total.saturatedFat * 100) / 100,
        cholesterol: Math.round(total.cholesterol * 100) / 100,
      },
    };
  }
}
