import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { canConvert, convertUnit } from '../common/utils/units';
import { AiService } from '../ai/ai.service';

type AnalysisStatus = 'exact_match' | 'covered_by_pantry' | 'substitutable' | 'missing';

@Injectable()
export class RecipeAnalysisService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly aiService: AiService,
  ) {}

  private async getAccessibleRecipe(id: number, userId: number) {
    const recipe = await this.prisma.recipe.findFirst({
      where: {
        id,
        OR: [
          { isPublic: true },
          { ownerId: userId },
          { shares: { some: { userId } } },
        ],
      },
      include: {
        ingredients: {
          include: {
            product: true,
          },
          orderBy: { id: 'asc' },
        },
      },
    });

    if (!recipe) {
      throw new NotFoundException(`Recipe with id ${id} not found`);
    }

    return recipe;
  }

  private calculateAvailableQuantity(
    inventoryItems: Array<{ quantity: any; unit: string }>,
    requiredUnit: string,
  ): number {
    if (!requiredUnit) {
      return inventoryItems.reduce((sum, item) => sum + Number(item.quantity ?? 0), 0);
    }

    const normalizedRequiredUnit = requiredUnit.trim().toLowerCase();

    const sameUnit = inventoryItems
      .filter((item) => item.unit.trim().toLowerCase() === normalizedRequiredUnit)
      .reduce((sum, item) => sum + Number(item.quantity ?? 0), 0);

    const converted = inventoryItems
      .filter((item) => item.unit.trim().toLowerCase() !== normalizedRequiredUnit)
      .reduce((sum, item) => {
        if (!canConvert(item.unit, requiredUnit)) return sum;
        try {
          return sum + convertUnit(Number(item.quantity ?? 0), item.unit, requiredUnit);
        } catch {
          return sum;
        }
      }, 0);

    return sameUnit + converted;
  }

  async analyzeRecipeIngredients(id: number, userId: number) {
    const recipe = await this.getAccessibleRecipe(id, userId);

    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { aiEngineEnabled: true },
    });

    const pantryItems = await this.prisma.pantryItem.findMany({
      where: { userId },
      select: { productId: true },
    });
    const pantryProductIds = new Set(pantryItems.map((p) => p.productId));

    const userInventory = await this.prisma.inventoryItem.findMany({
      where: { product: { ownerId: userId } },
      select: { productId: true },
    });
    const availableProductIds = new Set<number>([
      ...pantryItems.map((p) => p.productId),
      ...userInventory.map((i) => i.productId),
    ]);
    const availableProducts = availableProductIds.size > 0
      ? await this.prisma.product.findMany({
          where: { id: { in: Array.from(availableProductIds) }, isActive: true },
          select: { id: true, name: true, canonicalName: true },
        })
      : [];

    const ingredients = await Promise.all(
      recipe.ingredients.map(async (ingredient: any) => {
        const requiredQuantity = Number(ingredient.quantity ?? 0);
        const requiredUnit = (ingredient.unit ?? '').trim();
        const rawName = (ingredient.rawName ?? '').trim() || 'Okänd ingrediens';

        if (!ingredient.productId || !ingredient.product) {
          const aiMatches = user?.aiEngineEnabled ? await this.aiService.suggestIngredientMatches(rawName, availableProducts) : [];
          const aiBest = aiMatches[0];
          if (aiBest) {
            const matched = availableProducts.find((p) => p.id === aiBest.productId);
            return {
              ingredientId: ingredient.id,
              rawName,
              quantity: requiredQuantity,
              unit: requiredUnit,
              note: ingredient.note ?? null,
              status: 'substitutable' as AnalysisStatus,
              matchedProductId: aiBest.productId,
              matchedProductName: matched?.canonicalName || matched?.name || null,
              source: 'ai_match',
              availableQuantity: 0,
              missingQuantity: requiredQuantity,
            };
          }

          return {
            ingredientId: ingredient.id,
            rawName,
            quantity: requiredQuantity,
            unit: requiredUnit,
            note: ingredient.note ?? null,
            status: 'missing' as AnalysisStatus,
            matchedProductId: null,
            matchedProductName: null,
            source: null,
            availableQuantity: 0,
            missingQuantity: requiredQuantity,
          };
        }

        if (pantryProductIds.has(ingredient.productId)) {
          return {
            ingredientId: ingredient.id,
            rawName,
            quantity: requiredQuantity,
            unit: requiredUnit,
            note: ingredient.note ?? null,
            status: 'covered_by_pantry' as AnalysisStatus,
            matchedProductId: ingredient.productId,
            matchedProductName: ingredient.product.canonicalName || ingredient.product.name,
            source: 'pantry',
            availableQuantity: requiredQuantity,
            missingQuantity: 0,
          };
        }

        const inventoryItems = await this.prisma.inventoryItem.findMany({
          where: { productId: ingredient.productId, product: { ownerId: userId } },
          select: { quantity: true, unit: true },
        });

        const availableQuantity = this.calculateAvailableQuantity(inventoryItems, requiredUnit);
        if (availableQuantity >= requiredQuantity) {
          return {
            ingredientId: ingredient.id,
            rawName,
            quantity: requiredQuantity,
            unit: requiredUnit,
            note: ingredient.note ?? null,
            status: 'exact_match' as AnalysisStatus,
            matchedProductId: ingredient.productId,
            matchedProductName: ingredient.product.canonicalName || ingredient.product.name,
            source: 'inventory',
            availableQuantity,
            missingQuantity: 0,
          };
        }

        const alternativeProductIds = Array.isArray(ingredient.alternativeProductIds)
          ? ingredient.alternativeProductIds.filter((id: any) => typeof id === 'number')
          : [];

        for (const altProductId of alternativeProductIds) {
          if (pantryProductIds.has(altProductId)) {
            const altProduct = await this.prisma.product.findUnique({
              where: { id: altProductId },
              select: { id: true, name: true, canonicalName: true },
            });

            return {
              ingredientId: ingredient.id,
              rawName,
              quantity: requiredQuantity,
              unit: requiredUnit,
              note: ingredient.note ?? null,
              status: 'substitutable' as AnalysisStatus,
              matchedProductId: altProduct?.id ?? altProductId,
              matchedProductName: altProduct?.canonicalName || altProduct?.name || null,
              source: 'pantry_substitute',
              availableQuantity: requiredQuantity,
              missingQuantity: 0,
            };
          }

          const altInventoryItems = await this.prisma.inventoryItem.findMany({
            where: { productId: altProductId },
            select: { quantity: true, unit: true },
          });
          const altAvailable = this.calculateAvailableQuantity(altInventoryItems, requiredUnit);
          if (altAvailable > 0) {
            const altProduct = await this.prisma.product.findUnique({
              where: { id: altProductId },
              select: { id: true, name: true, canonicalName: true },
            });

            return {
              ingredientId: ingredient.id,
              rawName,
              quantity: requiredQuantity,
              unit: requiredUnit,
              note: ingredient.note ?? null,
              status: 'substitutable' as AnalysisStatus,
              matchedProductId: altProduct?.id ?? altProductId,
              matchedProductName: altProduct?.canonicalName || altProduct?.name || null,
              source: 'inventory_substitute',
              availableQuantity: altAvailable,
              missingQuantity: Math.max(0, requiredQuantity - altAvailable),
            };
          }
        }

        const aiSubs = user?.aiEngineEnabled ? await this.aiService.suggestSubstitutions(rawName, availableProducts) : [];
        const aiBestSub = aiSubs[0];
        if (aiBestSub) {
          const aiProduct = availableProducts.find((p) => p.id === aiBestSub.productId);
          return {
            ingredientId: ingredient.id,
            rawName,
            quantity: requiredQuantity,
            unit: requiredUnit,
            note: ingredient.note ?? null,
            status: 'substitutable' as AnalysisStatus,
            matchedProductId: aiBestSub.productId,
            matchedProductName: aiProduct?.canonicalName || aiProduct?.name || null,
            source: 'ai_substitute',
            availableQuantity,
            missingQuantity: Math.max(0, requiredQuantity - availableQuantity),
          };
        }

        return {
          ingredientId: ingredient.id,
          rawName,
          quantity: requiredQuantity,
          unit: requiredUnit,
          note: ingredient.note ?? null,
          status: 'missing' as AnalysisStatus,
          matchedProductId: ingredient.productId,
          matchedProductName: ingredient.product.canonicalName || ingredient.product.name,
          source: null,
          availableQuantity,
          missingQuantity: Math.max(0, requiredQuantity - availableQuantity),
        };
      }),
    );

    const summary = {
      exactCount: ingredients.filter((i) => i.status === 'exact_match').length,
      pantryCount: ingredients.filter((i) => i.status === 'covered_by_pantry').length,
      substituteCount: ingredients.filter((i) => i.status === 'substitutable').length,
      missingCount: ingredients.filter((i) => i.status === 'missing').length,
    };

    const shoppingListCandidates = ingredients
      .filter((i) => i.status === 'missing')
      .map((i) => ({
        ingredientId: i.ingredientId,
        rawName: i.rawName,
        quantity: i.quantity,
        unit: i.unit,
        missingQuantity: i.missingQuantity,
      }));

    return {
      recipeId: recipe.id,
      ingredients,
      summary,
      shoppingListCandidates,
    };
  }

  async rematchRecipeIngredients(id: number, userId: number) {
    return this.analyzeRecipeIngredients(id, userId);
  }
}
