using Microsoft.EntityFrameworkCore; using eprintServer.Data; using eprintServer.Entities; using eprintServer.Requests; namespace eprintServer.Services { public class OfferService { private readonly DataContext _context; public OfferService(DataContext context) { _context = context; } private IQueryable GetOfferQuery() { return _context.Offers .Include(o => o.BannerImage) .Include(o => o.Products) .ThenInclude(op => op.ProductImage); } public async Task> GetAllOffersAsync() { // Dynamically load Offer details var offers = await GetOfferQuery() .Select(o => new Offer { Id = o.Id, Type = o.Type, Status = o.Status, OfferPrice = o.OfferPrice, OriginalTotal = o.OriginalTotal, DiscountAmount = o.DiscountAmount, DiscountType = o.DiscountType, DiscountValue = o.DiscountValue, DiscountPercentage = o.DiscountPercentage, StartDate = o.StartDate, EndDate = o.EndDate, IsFeatured = o.IsFeatured, IsActive = o.IsActive, IsPublic = o.IsPublic, MaxUses = o.MaxUses, CreatedAt = o.CreatedAt, UpdatedAt = o.UpdatedAt, BannerImageId = o.BannerImageId, // Ensure FK is pulled BannerImage = o.BannerImage, // Ensure object is pulled Products = o.Products, // Manually fetch DynamicValue collections for display Title = _context.DynamicValues.Where(dv => EF.Property(dv, "OfferTitleId") == o.Id).ToList(), Description = _context.DynamicValues.Where(dv => EF.Property(dv, "OfferDescriptionId") == o.Id).ToList() }) .ToListAsync(); return offers; } public async Task GetOfferByIdAsync(int id) { // FIX: Use the shared query for comprehensive includes var offer = await GetOfferQuery() .FirstOrDefaultAsync(o => o.Id == id); if (offer == null) return null; // Manually populate DynamicValue collections offer.Title = await _context.DynamicValues.Where(dv => EF.Property(dv, "OfferTitleId") == offer.Id).ToListAsync(); offer.Description = await _context.DynamicValues.Where(dv => EF.Property(dv, "OfferDescriptionId") == offer.Id).ToListAsync(); return offer; } /* public async Task CreateOfferAsync(CreateOfferRequest request) { var offer = new Offer { Type = request.Type, Status = request.Status, OfferPrice = request.OfferPrice, OriginalTotal = request.OriginalTotal, DiscountAmount = request.DiscountAmount, DiscountType = request.DiscountType, DiscountValue = request.DiscountValue, DiscountPercentage = request.DiscountPercentage, StartDate = request.StartDate == default ? null : (DateTime?)request.StartDate.ToUniversalTime(), EndDate = request.EndDate == default ? null : (DateTime?)request.EndDate.ToUniversalTime(), IsFeatured = request.IsFeatured, IsActive = request.IsActive, IsPublic = request.IsPublic, MaxUses = request.MaxUses, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; _context.Offers.Add(offer); await _context.SaveChangesAsync(); await AddDynamicValues(offer.Id, request.Title, "OfferTitleId"); await AddDynamicValues(offer.Id, request.Description, "OfferDescriptionId"); if (request.Products != null && request.Products.Any()) { var offerProducts = request.Products.Select(p => new OfferProduct { OfferForeignKey = offer.Id, ProductId = p.ProductId, Quantity = p.Quantity, UnitPrice = p.UnitPrice, ProductName = p.ProductName }).ToList(); _context.OfferProducts.AddRange(offerProducts); } await _context.SaveChangesAsync(); // 4. Save linked entities return await GetOfferByIdAsync(offer.Id) ?? offer; } */ public async Task CreateOfferAsync(CreateOfferRequest request) { var offer = new Offer { Type = request.Type, Status = request.Status, OfferPrice = request.OfferPrice, OriginalTotal = request.OriginalTotal, DiscountAmount = request.DiscountAmount, DiscountType = request.DiscountType, DiscountValue = request.DiscountValue, DiscountPercentage = request.DiscountPercentage, StartDate = request.StartDate == default ? null : (DateTime?)request.StartDate.ToUniversalTime(), EndDate = request.EndDate == default ? null : (DateTime?)request.EndDate.ToUniversalTime(), IsFeatured = request.IsFeatured, IsActive = request.IsActive, IsPublic = request.IsPublic, MaxUses = request.MaxUses, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; _context.Offers.Add(offer); await _context.SaveChangesAsync(); await AddDynamicValues(offer.Id, request.Title, "OfferTitleId"); await AddDynamicValues(offer.Id, request.Description, "OfferDescriptionId"); if (request.Products != null && request.Products.Any()) { var offerProducts = request.Products.Select(p => new OfferProduct { OfferForeignKey = offer.Id, ProductId = p.ProductId, Quantity = p.Quantity, UnitPrice = p.UnitPrice, IsCustomizableProduct = p.IsCustomizableProduct, ProductName = p.ProductName }).ToList(); _context.OfferProducts.AddRange(offerProducts); } await _context.SaveChangesAsync(); return await GetOfferByIdAsync(offer.Id) ?? offer; } public async Task UpdateOfferAsync(int id, UpdateOfferRequest request) { // FIX: Use the shared query for comprehensive includes var offer = await GetOfferQuery() .FirstOrDefaultAsync(o => o.Id == id); if (offer == null) { throw new KeyNotFoundException($"Offer with ID {id} not found."); } if (!string.IsNullOrWhiteSpace(request.Type)) { offer.Type = request.Type; } if (!string.IsNullOrWhiteSpace(request.Status)) { offer.Status = request.Status; } if (!string.IsNullOrWhiteSpace(request.DiscountType)) { offer.DiscountType = request.DiscountType; } if (request.OfferPrice != default) { offer.OfferPrice = request.OfferPrice; } if (request.OriginalTotal != default) { offer.OriginalTotal = request.OriginalTotal; } if (request.DiscountAmount != default) { offer.DiscountAmount = request.DiscountAmount; } if (request.DiscountValue != default) { offer.DiscountValue = request.DiscountValue; } if (request.DiscountPercentage != default) { offer.DiscountPercentage = request.DiscountPercentage; } if (request.StartDate != default) { offer.StartDate = request.StartDate.ToUniversalTime(); } if (request.EndDate != default) { offer.EndDate = request.EndDate.ToUniversalTime(); } offer.IsFeatured = request.IsFeatured; offer.IsActive = request.IsActive; offer.IsPublic = request.IsPublic; offer.MaxUses = request.MaxUses; offer.UpdatedAt = DateTime.UtcNow; if (request.Title != null) { await UpdateDynamicValues(offer.Id, request.Title, "OfferTitleId"); } if (request.Description != null) { await UpdateDynamicValues(offer.Id, request.Description, "OfferDescriptionId"); } // 3. Handle Offer Products Update (Delete existing, then add new) // If the Products list is provided (even if empty), we replace the entire collection. if (request.Products != null) { // Delete existing ones _context.OfferProducts.RemoveRange(offer.Products); if (request.Products.Any()) { var newProducts = request.Products.Select(p => new OfferProduct { OfferForeignKey = offer.Id, ProductId = p.ProductId, Quantity = p.Quantity, UnitPrice = p.UnitPrice, ProductName = p.ProductName, IsCustomizableProduct = p.IsCustomizableProduct }).ToList(); _context.OfferProducts.AddRange(newProducts); } } await _context.SaveChangesAsync(); // FIX: Return the fully loaded entity with all includes return await GetOfferByIdAsync(offer.Id) ?? offer; } public async Task DeleteOfferAsync(int id) { var offer = await _context.Offers.FindAsync(id); if (offer == null) { throw new KeyNotFoundException($"Offer with ID {id} not found."); } // Explicitly delete DynamicValues related to this Offer await DeleteDynamicValues(id, "OfferTitleId"); await DeleteDynamicValues(id, "OfferDescriptionId"); // Delete the offer, which will cascade delete OfferProducts _context.Offers.Remove(offer); await _context.SaveChangesAsync(); } public async Task ChangeOfferStatusAsync(int id, string newStatus) { var offer = await _context.Offers.FindAsync(id); if (offer == null) { throw new KeyNotFoundException($"Offer with ID {id} not found."); } offer.Status = newStatus; offer.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); } // --- DynamicValue Helper Methods --- private async Task AddDynamicValues(int offerId, ICollection requests, string fkColumnName) { if (requests == null) return; foreach (var req in requests) { var dv = new DynamicValue { LanguageTag = req.LanguageTag, Value = req.Value }; // Use EF.Property to set the shadow property (FK) value _context.Entry(dv).Property(fkColumnName).CurrentValue = offerId; _context.DynamicValues.Add(dv); } } private async Task UpdateDynamicValues(int offerId, ICollection requests, string fkColumnName) { // 1. Delete all existing ones await DeleteDynamicValues(offerId, fkColumnName); // 2. Add the new ones if (requests != null && requests.Any()) { await AddDynamicValues(offerId, requests, fkColumnName); } } private async Task DeleteDynamicValues(int offerId, string fkColumnName) { var existingDVs = await _context.DynamicValues // Use EF.Property to query the shadow property .Where(dv => EF.Property(dv, fkColumnName) == offerId) .ToListAsync(); _context.DynamicValues.RemoveRange(existingDVs); } public async Task> GetAllOffersAsync( bool? active = null, bool? isFeatured = null, bool? isPublic = null) { // Start with the base query (which handles Includes) var query = GetOfferQuery(); // 1. Apply Filtering for IsActive if (active.HasValue) { query = query.Where(o => o.IsActive == active.Value); } // 2. Apply Filtering for IsFeatured if (isFeatured.HasValue) { query = query.Where(o => o.IsFeatured == isFeatured.Value); } // 3. Apply Filtering for IsPublic if (isPublic.HasValue) { query = query.Where(o => o.IsPublic == isPublic.Value); } // Select and materialize the query results var offers = await query .Select(o => new Offer { Id = o.Id, Type = o.Type, Status = o.Status, OfferPrice = o.OfferPrice, OriginalTotal = o.OriginalTotal, DiscountAmount = o.DiscountAmount, DiscountType = o.DiscountType, DiscountValue = o.DiscountValue, DiscountPercentage = o.DiscountPercentage, StartDate = o.StartDate, EndDate = o.EndDate, IsFeatured = o.IsFeatured, IsActive = o.IsActive, IsPublic = o.IsPublic, MaxUses = o.MaxUses, CreatedAt = o.CreatedAt, UpdatedAt = o.UpdatedAt, BannerImageId = o.BannerImageId, BannerImage = o.BannerImage, Products = o.Products, // Manually fetching DynamicValue collections Title = _context.DynamicValues.Where(dv => EF.Property(dv, "OfferTitleId") == o.Id).ToList(), Description = _context.DynamicValues.Where(dv => EF.Property(dv, "OfferDescriptionId") == o.Id).ToList() }) .ToListAsync(); return offers; } } }