using Meezi.Core.Branding; using Meezi.Core.Entities; using Meezi.Core.Enums; using Meezi.Infrastructure.Branding; using Meezi.Infrastructure.Discover; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Meezi.Infrastructure.Data; /// Seeds 30 Persian showcase cafés for public discover (development only). public static class DiscoverShowcaseSeeder { // Approximate city centres. Each café is scattered around its city with a // small deterministic offset (derived from its id) so the marketing map // shows a realistic cluster of blinking lights instead of one stacked dot. private static readonly Dictionary CityGeo = new() { ["تهران"] = (35.70, 51.39, 0.13), ["کرج"] = (35.83, 50.99, 0.07), }; private static (double Lat, double Lng) GeoFor(string id, string city) { var (lat, lng, spread) = CityGeo.TryGetValue(city, out var g) ? g : (35.70, 51.39, 0.13); unchecked { var h = 17; foreach (var ch in id) h = (h * 31) + ch; var ox = (((h & 0xFFFF) / 65535.0) - 0.5) * 2 * spread; var oy = ((((h >> 16) & 0xFFFF) / 65535.0) - 0.5) * 2 * spread; return (Math.Round(lat + oy, 5), Math.Round(lng + ox, 5)); } } private static readonly string[] ReviewAuthors = ["سارا", "علی", "مینا", "رضا", "نازنین"]; private static readonly string[] ReviewComments = [ "فضا و نوشیدنی عالی بود.", "سرویس سریع، پیشنهاد می‌کنم.", "مناسب قرار و کار.", "قیمت مناسب برای کیفیت.", "دسر و قهوه خوشمزه بود.", ]; public static async Task SeedAsync(AppDbContext db, ILogger logger) { var addedCafes = 0; foreach (var spec in DiscoverShowcaseCatalog.Cafes) { var cafe = await db.Cafes.FirstOrDefaultAsync(c => c.Id == spec.Id); var (geoLat, geoLng) = GeoFor(spec.Id, spec.City); if (cafe is null) { cafe = new Cafe { Id = spec.Id, Name = spec.Name, NameEn = spec.Slug.Replace('-', ' '), Slug = spec.Slug, City = spec.City, Address = spec.Address, Latitude = geoLat, Longitude = geoLng, Description = spec.Description, PlanTier = spec.PlanTier, PreferredLanguage = "fa", IsVerified = true, DiscoverProfileJson = CafeDiscoverProfileSerializer.Serialize(spec.Profile), DiscoverBadgesJson = DiscoverBadgesSerializer.Serialize(spec.Badges), ThemeJson = CafeThemeSerializer.Serialize(new CafeTheme { PaletteId = CafeThemeDefaults.PaletteMeeziGreen, PanelStyle = CafeThemeDefaults.PanelModern, MenuStyle = CafeThemeDefaults.MenuCards, Density = CafeThemeDefaults.DensityComfortable, Radius = CafeThemeDefaults.RadiusMd }) }; db.Cafes.Add(cafe); var branchId = BranchId(spec.Id); db.Branches.Add(new Branch { Id = branchId, CafeId = spec.Id, Name = "شعبه اصلی", City = spec.City, Address = spec.Address, Phone = "02100000000", IsActive = true, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }); db.Employees.Add(new Employee { Id = OwnerId(spec.Id), CafeId = spec.Id, BranchId = branchId, Name = $"مدیر {spec.Name}", Phone = spec.OwnerPhone, Role = EmployeeRole.Owner, BaseSalary = 0 }); await db.SaveChangesAsync(); addedCafes++; } else { var changed = false; if (string.IsNullOrEmpty(cafe.DiscoverProfileJson)) { cafe.DiscoverProfileJson = CafeDiscoverProfileSerializer.Serialize(spec.Profile); changed = true; } if (string.IsNullOrEmpty(cafe.DiscoverBadgesJson) && spec.Badges is { Count: > 0 }) { cafe.DiscoverBadgesJson = DiscoverBadgesSerializer.Serialize(spec.Badges); changed = true; } if (!cafe.IsVerified) { cafe.IsVerified = true; changed = true; } if (cafe.Latitude is null || cafe.Longitude is null) { cafe.Latitude = geoLat; cafe.Longitude = geoLng; changed = true; } if (changed) await db.SaveChangesAsync(); } await EnsureShowcaseMenuAsync(db, spec, logger); await EnsureShowcaseReviewsAsync(db, spec.Id, logger); } if (addedCafes > 0) logger.LogInformation("Discover showcase seed: {Count} new cafés", addedCafes); } private static string BranchId(string cafeId) => $"branch_{cafeId}_main"; private static string OwnerId(string cafeId) => $"emp_{cafeId}_owner"; private static async Task EnsureShowcaseMenuAsync( AppDbContext db, DiscoverShowcaseCatalog.ShowcaseCafe spec, ILogger logger) { var taxId = $"tax_{spec.Id}"; if (!await db.Taxes.AnyAsync(t => t.Id == taxId)) { db.Taxes.Add(new Tax { Id = taxId, CafeId = spec.Id, Name = "مالیات", Rate = 9, IsDefault = true, IsRequired = true, IsCompound = false }); await db.SaveChangesAsync(); } var templateIndex = spec.MenuTemplateIndex % DiscoverShowcaseMenus.Templates.Count; var template = DiscoverShowcaseMenus.Templates[templateIndex]; var existingCats = await db.MenuCategories .Where(c => c.CafeId == spec.Id) .Select(c => c.Id) .ToListAsync(); var catsAdded = 0; foreach (var cat in template.Categories) { var catId = Prefixed(spec.Id, cat.Id); if (existingCats.Contains(catId)) continue; db.MenuCategories.Add(new MenuCategory { Id = catId, CafeId = spec.Id, Name = cat.Name, NameEn = cat.NameEn, NameAr = cat.NameAr, Icon = cat.Icon, IconPresetId = cat.IconPresetId, IconStyle = cat.IconStyle, SortOrder = cat.SortOrder, TaxId = taxId, IsActive = true }); catsAdded++; } if (catsAdded > 0) await db.SaveChangesAsync(); var existingItems = await db.MenuItems .Where(i => i.CafeId == spec.Id) .Select(i => i.Id) .ToListAsync(); var itemsAdded = 0; foreach (var item in template.Items) { var itemId = Prefixed(spec.Id, item.Id); if (existingItems.Contains(itemId)) continue; var catId = Prefixed(spec.Id, item.CategoryId); db.MenuItems.Add(new MenuItem { Id = itemId, CafeId = spec.Id, CategoryId = catId, Name = item.Name, NameEn = item.NameEn, NameAr = item.NameAr, Description = item.Description, Price = item.PriceToman, DiscountPercent = item.DiscountPercent, ImageUrl = DemoMenuCatalog.ResolveItemImageUrl(item), IsAvailable = true }); itemsAdded++; } if (itemsAdded > 0) { await db.SaveChangesAsync(); logger.LogInformation( "Showcase menu: cafe {Slug} +{Cats} cats +{Items} items", spec.Slug, catsAdded, itemsAdded); } } private static async Task EnsureShowcaseReviewsAsync(AppDbContext db, string cafeId, ILogger logger) { if (await db.CafeReviews.CountAsync(r => r.CafeId == cafeId) >= 2) return; var rng = new Random(cafeId.GetHashCode(StringComparison.Ordinal)); var count = 2 - await db.CafeReviews.CountAsync(r => r.CafeId == cafeId); for (var i = 0; i < count; i++) { db.CafeReviews.Add(new CafeReview { CafeId = cafeId, AuthorName = ReviewAuthors[rng.Next(ReviewAuthors.Length)], Rating = rng.Next(4, 6), Comment = ReviewComments[rng.Next(ReviewComments.Length)], CreatedAt = DateTime.UtcNow.AddDays(-rng.Next(1, 30)) }); } await db.SaveChangesAsync(); logger.LogDebug("Showcase reviews seeded for {CafeId}", cafeId); } private static string Prefixed(string cafeId, string seedId) => $"{cafeId}_{seedId}"; }