import { RecipeModel } from '../../schemas/recipe.schema.js';
import type { CreateRecipeInput, UpdateRecipeInput, RecipeQueryInput } from '@meshitrack/shared';
import type { RecipeIngredient as StoredIngredient } from '@meshitrack/shared';

interface NutritionInfo {
  calories: number;
  protein: number;
  carbs: number;
  fat: number;
  fiber?: number;
  sugar?: number;
  sodium?: number;
  saturatedFat?: number;
  cholesterol?: number;
}

interface ComputedFields {
  ingredients: StoredIngredient[];
  totalNutrition: NutritionInfo;
  perServingNutrition: NutritionInfo;
  warnings: string[];
}

export class RecipesRepository {
  public async findByHousehold(householdId: string, query: RecipeQueryInput) {
    const filter: Record<string, unknown> = { householdId, deletedAt: { $exists: false } };

    if (query.q) filter['$text'] = { $search: query.q };
    if (query.cuisine) filter['cuisine'] = { $regex: query.cuisine, $options: 'i' };
    if (query.isFavorite !== undefined) filter['isFavorite'] = query.isFavorite;
    if (query.maxCalories !== undefined)
      filter['perServingNutrition.calories'] = { $lte: query.maxCalories };

    if (query.tags) {
      const tagList = query.tags
        .split(',')
        .map((t) => t.trim())
        .filter(Boolean);
      if (tagList.length > 0) filter['tags'] = { $all: tagList };
    }

    if (query.cursor) {
      const id = Buffer.from(query.cursor, 'base64').toString();
      filter['_id'] = { $gt: id };
    }

    const limit = query.limit;
    const items = await RecipeModel.find(filter)
      .sort({ _id: 1 })
      .limit(limit + 1)
      .lean()
      .exec();

    const hasMore = items.length > limit;
    const data = hasMore ? items.slice(0, limit) : items;
    const cursor =
      data.length > 0 ? Buffer.from(data[data.length - 1]._id.toString()).toString('base64') : null;

    return { data, pagination: { cursor: hasMore ? cursor : null, hasMore } };
  }

  public async findById(id: string, householdId: string) {
    return RecipeModel.findOne({ _id: id, householdId, deletedAt: { $exists: false } })
      .lean()
      .exec();
  }

  public async findByProductId(
    householdId: string,
    productId: string,
    query: { cursor?: string; limit?: number },
  ) {
    const filter: Record<string, unknown> = {
      householdId,
      'ingredients.productId': productId,
      deletedAt: { $exists: false },
    };

    const limit = query.limit ?? 20;
    if (query.cursor) {
      const id = Buffer.from(query.cursor, 'base64').toString();
      filter['_id'] = { $gt: id };
    }

    const items = await RecipeModel.find(filter)
      .sort({ _id: 1 })
      .limit(limit + 1)
      .lean()
      .exec();
    const hasMore = items.length > limit;
    const data = hasMore ? items.slice(0, limit) : items;
    const cursor =
      data.length > 0 ? Buffer.from(data[data.length - 1]._id.toString()).toString('base64') : null;

    return { data, pagination: { cursor: hasMore ? cursor : null, hasMore } };
  }

  public async findAllByProductId(householdId: string, productId: string) {
    return RecipeModel.find({
      householdId,
      'ingredients.productId': productId,
      deletedAt: { $exists: false },
    })
      .lean()
      .exec();
  }

  public async create(
    data: Omit<CreateRecipeInput, 'ingredients'>,
    computed: ComputedFields,
    householdId: string,
    createdBy: string,
  ) {
    const recipe = new RecipeModel({ ...data, ...computed, householdId, createdBy });
    const saved = await recipe.save();
    return saved.toObject();
  }

  public async update(
    id: string,
    householdId: string,
    data: Partial<UpdateRecipeInput>,
    computed?: Partial<ComputedFields>,
  ) {
    const update: Record<string, unknown> = { ...data };
    if (computed) Object.assign(update, computed);
    return RecipeModel.findOneAndUpdate(
      { _id: id, householdId, deletedAt: { $exists: false } },
      { $set: update },
      { new: true, lean: true },
    ).exec();
  }

  public async softDelete(id: string, householdId: string) {
    return RecipeModel.findOneAndUpdate(
      { _id: id, householdId, deletedAt: { $exists: false } },
      { $set: { deletedAt: new Date() } },
      { new: true, lean: true },
    ).exec();
  }
}
