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

namespace MealPlan.Api.Services;

/// <summary>
/// Read-only recipe queries. All filtering is done with EF Core LINQ (parameterized);
/// no raw SQL is used anywhere.
/// </summary>
public class RecipeService : IRecipeService
{
    private readonly AppDbContext _db;

    public RecipeService(AppDbContext db) => _db = db;

    public async Task<IReadOnlyList<RecipeListItemDto>> GetRecipesAsync(
        int? category, string? search, string? ingredient, CancellationToken ct = default)
    {
        var query = _db.Recipes.AsNoTracking().Where(r => r.IsActive);

        if (category is >= 0 and <= 3)
        {
            query = query.Where(r => r.MealCategory == category);
        }

        if (!string.IsNullOrWhiteSpace(search))
        {
            var term = search.Trim();
            query = query.Where(r => EF.Functions.ILike(r.Name, $"%{term}%"));
        }

        if (!string.IsNullOrWhiteSpace(ingredient))
        {
            var ingredientTerm = ingredient.Trim();
            query = query.Where(r =>
                r.Ingredients.Any(i => EF.Functions.ILike(i.Name, $"%{ingredientTerm}%")));
        }

        return await query
            .OrderBy(r => r.Name)
            .Select(r => new RecipeListItemDto
            {
                Id = r.Id,
                Name = r.Name,
                MealCategory = r.MealCategory,
                Calories = r.Calories,
                PrepTimeMinutes = r.PrepTimeMinutes,
            })
            .ToListAsync(ct);
    }

    public async Task<RecipeDetailDto?> GetRecipeByIdAsync(int id, CancellationToken ct = default)
    {
        return await _db.Recipes
            .AsNoTracking()
            .Where(r => r.Id == id && r.IsActive)
            .Select(r => new RecipeDetailDto
            {
                Id = r.Id,
                Name = r.Name,
                MealCategory = r.MealCategory,
                Calories = r.Calories,
                Protein = r.Protein,
                Fat = r.Fat,
                Carbs = r.Carbs,
                PrepTimeMinutes = r.PrepTimeMinutes,
                Ingredients = r.Ingredients
                    .OrderBy(i => i.SortOrder)
                    .ThenBy(i => i.Id)
                    .Select(i => new IngredientDto
                    {
                        Id = i.Id,
                        Name = i.Name,
                        AmountGrams = i.AmountGrams,
                        Unit = i.Unit,
                        SortOrder = i.SortOrder,
                        CatalogItemId = i.CatalogItemId,
                    })
                    .ToList(),
                Steps = r.Steps
                    .OrderBy(s => s.StepNumber)
                    .Select(s => new RecipeStepDto
                    {
                        Id = s.Id,
                        StepNumber = s.StepNumber,
                        Description = s.Description,
                    })
                    .ToList(),
            })
            .FirstOrDefaultAsync(ct);
    }

    public async Task<IReadOnlyList<CategoryCountDto>> GetCategoriesAsync(CancellationToken ct = default)
    {
        var counts = await _db.Recipes
            .AsNoTracking()
            .Where(r => r.IsActive)
            .GroupBy(r => r.MealCategory)
            .Select(g => new { Category = g.Key, Count = g.Count() })
            .ToListAsync(ct);

        // Always return all four categories, even when a category has zero recipes.
        return Enum.GetValues<MealCategory>()
            .Select(category => new CategoryCountDto
            {
                Category = (int)category,
                Name = category.ToString(),
                Count = counts.FirstOrDefault(c => c.Category == (int)category)?.Count ?? 0,
            })
            .OrderBy(c => c.Category)
            .ToList();
    }
}
