using MealPlan.Api.Data;
using MealPlan.Api.Models;
using Microsoft.EntityFrameworkCore;

namespace MealPlan.Api.Services;

public readonly record struct MacroTotals(int Calories, decimal Protein, decimal Fat, decimal Carbs);

public static class RecipeMacroCalculator
{
    public static bool IsSpiceUnit(string? unit)
    {
        if (string.IsNullOrWhiteSpace(unit)) return false;
        var u = unit.Trim().ToLowerInvariant();
        return u is "do smaku" or "optional" or "opcjonalnie" or "szczypta" or "to taste";
    }

    public static bool IsQuantifiedMacroIngredient(decimal? amountGrams, string? unit, int? catalogItemId) =>
        catalogItemId.HasValue && amountGrams is > 0 && !IsSpiceUnit(unit);

    public static async Task<MacroTotals?> ComputeFromCatalogAsync(
        AppDbContext db,
        IEnumerable<(decimal? AmountGrams, string? Unit, int? CatalogItemId)> ingredients,
        CancellationToken ct = default)
    {
        var rows = ingredients.ToList();
        var ids = rows
            .Where(i => IsQuantifiedMacroIngredient(i.AmountGrams, i.Unit, i.CatalogItemId))
            .Select(i => i.CatalogItemId!.Value)
            .Distinct()
            .ToList();
        if (ids.Count == 0) return null;

        var catalog = await db.IngredientNutritionCatalog.AsNoTracking()
            .Where(i => ids.Contains(i.Id) && i.IsActive)
            .ToDictionaryAsync(i => i.Id, ct);

        int calories = 0;
        decimal protein = 0, fat = 0, carbs = 0;
        foreach (var ing in rows)
        {
            if (!IsQuantifiedMacroIngredient(ing.AmountGrams, ing.Unit, ing.CatalogItemId)) continue;
            if (!catalog.TryGetValue(ing.CatalogItemId!.Value, out var cat)) continue;

            var scale = ing.AmountGrams!.Value / 100m;
            calories += (int)Math.Round(cat.CaloriesPer100G * scale);
            protein += cat.ProteinGPer100G * scale;
            fat += cat.FatGPer100G * scale;
            carbs += cat.CarbsGPer100G * scale;
        }

        if (calories <= 0) return null;
        return new MacroTotals(calories, Math.Round(protein, 2), Math.Round(fat, 2), Math.Round(carbs, 2));
    }

    public static decimal ResolveScaleFactor(int currentCalories, int targetCalories, int toleranceKcal)
    {
        if (currentCalories <= 0 || targetCalories <= 0) return 1m;
        if (Math.Abs(currentCalories - targetCalories) <= toleranceKcal) return 1m;
        return Math.Clamp((decimal)targetCalories / currentCalories, 0.25m, 4m);
    }

    public static void ScaleIngredientAmounts(
        IList<Ingredient> ingredients,
        decimal scaleFactor,
        int toleranceKcal,
        int targetCalories,
        MacroTotals? currentTotals)
    {
        if (scaleFactor == 1m) return;
        if (currentTotals is null || currentTotals.Value.Calories <= 0) return;
        if (Math.Abs(currentTotals.Value.Calories - targetCalories) <= toleranceKcal) return;

        foreach (var ing in ingredients)
        {
            if (!IsQuantifiedMacroIngredient(ing.AmountGrams, ing.Unit, ing.CatalogItemId)) continue;
            ing.AmountGrams = Math.Round(ing.AmountGrams!.Value * scaleFactor, 1);
        }
    }

    public static int CountUnlinkedMacroIngredients(IEnumerable<Ingredient> ingredients) =>
        ingredients.Count(i => i.AmountGrams is > 0 && !IsSpiceUnit(i.Unit) && !i.CatalogItemId.HasValue);
}
