using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Recepie.Data; using Recepie.Models; using Recepie.ViewModels; namespace Recepie.Controllers { public class WeekPlannerController : Controller { private readonly RecipeContext _context; public WeekPlannerController(RecipeContext context) { _context = context; } public async Task Index(DateTime? startDate = null) { var weekStart = startDate ?? DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + 1); // Monday var currentWeekPlan = await _context.WeekPlans .Include(wp => wp.DayPlans.OrderBy(dp => dp.Date)) .ThenInclude(dp => dp.Recipe) .FirstOrDefaultAsync(wp => wp.StartDate.Date == weekStart.Date); var mainIngredients = await GetMainIngredientsAsync(); var viewModel = new WeekPlannerViewModel { CurrentWeekPlan = currentWeekPlan, AvailableMainIngredients = mainIngredients, StartDate = weekStart }; return View(viewModel); } [HttpPost] public async Task CreateWeekPlan(DateTime startDate, string planName = "") { if (string.IsNullOrEmpty(planName)) planName = $"Week Plan {startDate:MMM dd, yyyy}"; var weekPlan = new WeekPlan { Name = planName, StartDate = startDate, CreatedAt = DateTime.Now }; _context.WeekPlans.Add(weekPlan); await _context.SaveChangesAsync(); // Create day plans for the week for (int i = 0; i < 7; i++) { var dayPlan = new DayPlan { WeekPlanId = weekPlan.Id, DayOfWeek = (DayOfWeek)((int)(startDate.DayOfWeek + i) % 7), Date = startDate.AddDays(i) }; _context.DayPlans.Add(dayPlan); } await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } public async Task ConfigureDay(int weekPlanId, DayOfWeek dayOfWeek) { var weekPlan = await _context.WeekPlans .Include(wp => wp.DayPlans) .FirstOrDefaultAsync(wp => wp.Id == weekPlanId); if (weekPlan == null) return NotFound(); var dayPlan = weekPlan.DayPlans.FirstOrDefault(dp => dp.DayOfWeek == dayOfWeek); if (dayPlan == null) return NotFound(); var mainIngredients = await GetMainIngredientsAsync(); var suggestedRecipes = await GetSuggestedRecipesAsync(dayPlan.MainIngredient, dayPlan.BannedIngredients, dayPlan.RequiredIngredients); var viewModel = new DayPlanConfigViewModel { DayPlanId = dayPlan.Id, DayOfWeek = dayPlan.DayOfWeek, Date = dayPlan.Date, SelectedMainIngredient = dayPlan.MainIngredient, BannedIngredients = dayPlan.BannedIngredients, RequiredIngredients = dayPlan.RequiredIngredients, AvailableMainIngredients = mainIngredients, SuggestedRecipes = suggestedRecipes, SelectedRecipeId = dayPlan.RecipeId }; return View(viewModel); } [HttpPost] public async Task UpdateDayPlan(DayPlanConfigViewModel model) { var dayPlan = await _context.DayPlans.FindAsync(model.DayPlanId); if (dayPlan == null) return NotFound(); dayPlan.MainIngredient = model.SelectedMainIngredient; dayPlan.BannedIngredients = model.BannedIngredients; dayPlan.RequiredIngredients = model.RequiredIngredients; dayPlan.RecipeId = model.SelectedRecipeId; await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } [HttpPost] public async Task RandomizeRecipe(int dayPlanId) { var dayPlan = await _context.DayPlans.FindAsync(dayPlanId); if (dayPlan == null) return NotFound(); // Get all recipes for this week to avoid repeats var weekPlan = await _context.WeekPlans .Include(wp => wp.DayPlans) .FirstOrDefaultAsync(wp => wp.DayPlans.Any(dp => dp.Id == dayPlanId)); var usedRecipeIds = weekPlan?.DayPlans .Where(dp => dp.RecipeId.HasValue && dp.Id != dayPlanId) .Select(dp => dp.RecipeId!.Value) .ToList() ?? new List(); var recipes = await GetSuggestedRecipesAsync(dayPlan.MainIngredient, dayPlan.BannedIngredients, dayPlan.RequiredIngredients); // Filter out already used recipes this week var availableRecipes = recipes.Where(r => !usedRecipeIds.Contains(r.Id)).ToList(); // If no unused recipes, fall back to all suggested recipes if (!availableRecipes.Any()) availableRecipes = recipes; if (availableRecipes.Any()) { // Use cryptographically secure random for better distribution using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); var randomBytes = new byte[4]; rng.GetBytes(randomBytes); var randomIndex = Math.Abs(BitConverter.ToInt32(randomBytes, 0)) % availableRecipes.Count; var randomRecipe = availableRecipes[randomIndex]; dayPlan.RecipeId = randomRecipe.Id; await _context.SaveChangesAsync(); } return RedirectToAction(nameof(Index)); } private async Task> GetMainIngredientsAsync() { // Define clean, consolidated main ingredient categories var mainIngredientCategories = new List { "Pasta", "Potatis", "Ris", "Kött", "Kyckling", "Fisk", "Skaldjur", "Ägg", "Bönor & Linser", "Vegetariskt", "Soppa", "Sallad" }; return mainIngredientCategories; } private async Task> GetSuggestedRecipesAsync(string? mainIngredient, string? bannedIngredients, string? requiredIngredients) { var query = _context.Recipes .Include(r => r.RecipeIngredients) .ThenInclude(ri => ri.Ingredient) .AsQueryable(); // Filter by main ingredient with smart categorization if (!string.IsNullOrEmpty(mainIngredient)) { var searchTerms = GetIngredientSearchTerms(mainIngredient); query = query.Where(r => r.RecipeIngredients .Any(ri => ri.Ingredient != null && ri.Ingredient.Name != null && searchTerms.Any(term => ri.Ingredient.Name.ToLower().Contains(term)))); } // Filter out banned ingredients if (!string.IsNullOrEmpty(bannedIngredients)) { var banned = bannedIngredients.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(b => b.Trim().ToLower()).ToList(); query = query.Where(r => !r.RecipeIngredients .Any(ri => ri.Ingredient != null && ri.Ingredient.Name != null && banned.Any(b => ri.Ingredient.Name!.ToLower().Contains(b)))); } // Filter by required ingredients if (!string.IsNullOrEmpty(requiredIngredients)) { var required = requiredIngredients.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(req => req.Trim().ToLower()).ToList(); foreach (var req in required) { query = query.Where(r => r.RecipeIngredients .Any(ri => ri.Ingredient != null && ri.Ingredient.Name != null && ri.Ingredient.Name.ToLower().Contains(req))); } } // Increase the pool significantly and add randomization at DB level return await query.OrderBy(r => Guid.NewGuid()).Take(50).ToListAsync(); } private List GetIngredientSearchTerms(string mainIngredient) { return mainIngredient.ToLower() switch { "pasta" => new List { "pasta", "spaghetti", "penne", "fusilli", "linguine", "tagliatelle", "fettuccine", "makaroner", "lasagne", "ravioli", "gnocchi" }, "potatis" => new List { "potatis", "potato", "potatismos", "klyftpotatis", "gratäng" }, "ris" => new List { "ris", "rice", "risotto", "basmati", "jasmin" }, "kött" => new List { "kött", "nötkött", "fläsk", "lamm", "köttfärs", "bacon", "skinka", "korv" }, "kyckling" => new List { "kyckling", "chicken", "kycklingfilé", "kycklingklubb", "kycklingbröst" }, "fisk" => new List { "fisk", "lax", "torsk", "abborre", "tonfisk", "salmon", "cod" }, "skaldjur" => new List { "räkor", "krabba", "hummer", "musslor", "shrimp", "crab", "lobster" }, "ägg" => new List { "ägg", "egg", "omelett", "scrambled" }, "bönor & linser" => new List { "bönor", "linser", "kikärtor", "beans", "lentils", "chickpeas" }, "vegetariskt" => new List { "tofu", "halloumi", "quinoa", "bulgur", "tempeh", "vegetarisk" }, "soppa" => new List { "soppa", "soup", "buljong", "bisque" }, "sallad" => new List { "sallad", "salad", "coleslaw", "caesar" }, _ => new List { mainIngredient.ToLower() } }; } } }