using Microsoft.EntityFrameworkCore; using eprintServer.Data; using eprintServer.Entities; using eprintServer.DTOs; using eprintServer.Responses; namespace eprintServer.Services; public class ProductCategoryService { private readonly DataContext _context; public ProductCategoryService(DataContext context) { _context = context; } private IQueryable GetCategoriesQuery() { return _context.ProductCategories .Include(pc => pc.Name) .Include(pc => pc.Image) .AsQueryable(); } private ProductCategory ConvertDtoToEntity(ProductCategoryDto dto) { var category = new ProductCategory { Archive = dto.Archive, Name = dto.Name.Select(n => new DynamicValue { Value = n.Value, LanguageTag = n.LanguageTag }).ToList() }; return category; } public async Task AddCategoryAsync(ProductCategoryDto dto) { var category = ConvertDtoToEntity(dto); _context.ProductCategories.Add(category); await _context.SaveChangesAsync(); return await GetCategoriesQuery().FirstOrDefaultAsync(pc => pc.Id == category.Id) ?? throw new InvalidOperationException("Failed to retrieve newly created category."); } public async Task GetCategoryByIdAsync(long id) { return await GetCategoriesQuery().FirstOrDefaultAsync(pc => pc.Id == id); } public async Task UpdateCategoryAsync(long categoryId, UpdateProductCategoryRequest request) { var category = await GetCategoriesQuery().FirstOrDefaultAsync(c => c.Id == categoryId); if (category == null) { return null; } bool changed = false; if (request.Archive.HasValue && request.Archive.Value != category.Archive) { category.Archive = request.Archive.Value; changed = true; } if (request.Name != null && request.Name.Any()) { _context.DynamicValues.RemoveRange(category.Name); category.Name.Clear(); // Add new names category.Name.AddRange(request.Name.Select(dv => new DynamicValue { LanguageTag = dv.LanguageTag, Value = dv.Value })); changed = true; } if (changed) { _context.ProductCategories.Update(category); await _context.SaveChangesAsync(); } return await GetCategoryByIdAsync(categoryId); } public async Task DeleteCategoryAsync(long id) { var category = await _context.ProductCategories.FindAsync(id); if (category == null) { return false; } _context.ProductCategories.Remove(category); await _context.SaveChangesAsync(); return true; } public async Task UpdateArchiveStatusAsync(long id) { var category = await _context.ProductCategories.FindAsync(id); if (category == null) { throw new KeyNotFoundException($"ProductCategory with ID {id} not found."); } // Set the ProductCategory to the inverse of what it has (if true, set it to false, and vice versa) category.Archive = !category.Archive; _context.ProductCategories.Update(category); await _context.SaveChangesAsync(); return await GetCategoriesQuery().FirstOrDefaultAsync(pc => pc.Id == id) ?? throw new InvalidOperationException("Failed to retrieve category after archive update."); } private IQueryable ApplyFilters(IQueryable query, string? search, bool? archive) { if (archive.HasValue) { query = query.Where(pc => pc.Archive == archive.Value); } if (!string.IsNullOrWhiteSpace(search)) { var searchLower = search.ToLower(); query = query.Where(pc => pc.Name.Any(n => n.Value.ToLower().Contains(searchLower))); } return query; } public async Task> GetAllCategoriesListAsync(string? search, bool? archive) { var query = GetCategoriesQuery(); query = ApplyFilters(query, search, archive); var categories = await query.ToListAsync(); return categories.OrderByDescending(pc => pc.Timestamp).ToList(); } /* public async Task> GetAllCategoriesPagedAsync(string? search, bool? archive, int page = 0, int size = 5) { var query = GetCategoriesQuery(); query = ApplyFilters(query, search, archive); var totalCount = await query.CountAsync(); var categories = await query.ToListAsync(); categories = categories.OrderByDescending(pc => pc.Timestamp).ToList(); var pagedItems = categories .Skip(page * size) .Take(size) .ToList(); return new PagedResult(pagedItems, totalCount, page, size); } */ public async Task> GetAllCategoriesPagedAsync(string? search, bool? archive, int page = 0, int size = 5) { var query = GetCategoriesQuery(); query = ApplyFilters(query, search, archive); var totalCount = await query.CountAsync(); // Do the sorting and paging in the database var pagedItems = await query .OrderByDescending(pc => pc.Timestamp) .Skip(page * size) .Take(size) .ToListAsync(); return new PagedResult(pagedItems, totalCount, page, size); } public async Task UpdateCategoryImageAsync(long categoryId, FileModel fileModel) { var category = await GetCategoryByIdAsync(categoryId); if (category == null) { return null; } // Use the SetImage helper method category.SetImage(fileModel); _context.ProductCategories.Update(category); await _context.SaveChangesAsync(); return category; } /* public async Task?> GetCategoryProductsPagedAsync(long categoryId, int page, int size) { // 1. Fetch category with both collections var category = await _context.ProductCategories .Include(pc => pc.Products) .Include(pc => pc.CustomizableProducts) .FirstOrDefaultAsync(pc => pc.Id == categoryId); if (category == null) return null; // 2. Calculate pagination int effectiveSize = size > 0 ? size : 10; int skip = page * effectiveSize; // 3. Extract paged subsets for each list var pagedProducts = category.Products .Skip(skip) .Take(effectiveSize) .ToList(); var pagedCustomizable = category.CustomizableProducts .Skip(skip) .Take(effectiveSize) .ToList(); // 4. Determine the highest count to represent the "Total" for the PagedResult // This ensures pagination continues if at least one list has more items int totalCount = Math.Max(category.Products.Count, category.CustomizableProducts.Count); var data = new CategoryProductsData { Products = pagedProducts, CustomizableProducts = pagedCustomizable }; // We return a PagedResult containing a List with ONE item: our CategoryProductsData object return new PagedResult( new List { data }, totalCount, page, effectiveSize ); } */ /* public async Task?> GetCategoryProductsPagedAsync(long categoryId, int page, int size) { var category = await _context.ProductCategories .Include(pc => pc.Name) // Load Category Names .Include(pc => pc.Products) .ThenInclude(p => p.Names) // Load Sellable Names .Include(pc => pc.Products) .ThenInclude(p => p.Descriptions) // Load Sellable Descriptions .Include(pc => pc.CustomizableProducts) .ThenInclude(cp => cp.Names) // Load Customizable Names .Include(pc => pc.CustomizableProducts) .ThenInclude(cp => cp.Descriptions) // Load Customizable Descriptions .FirstOrDefaultAsync(pc => pc.Id == categoryId); if (category == null) return null; int effectiveSize = size > 0 ? size : 10; int skip = page * effectiveSize; // IMPORTANT: Use the loaded collection var pagedProducts = category.Products .Skip(skip) .Take(effectiveSize) .ToList(); var pagedCustomizable = category.CustomizableProducts .Skip(skip) .Take(effectiveSize) .ToList(); int totalCount = Math.Max(category.Products.Count, category.CustomizableProducts.Count); var data = new CategoryProductsData { Products = pagedProducts, CustomizableProducts = pagedCustomizable }; return new PagedResult( new List { data }, totalCount, page, effectiveSize ); } */ public async Task?> GetCategoryProductsPagedAsync(long categoryId, int page, int size) { // 1. First, check if category exists and get total counts for paging var categoryExists = await _context.ProductCategories .AnyAsync(pc => pc.Id == categoryId); if (!categoryExists) return null; int effectiveSize = size > 0 ? size : 10; int skip = page * effectiveSize; // 2. Fetch Paged Sellable Products directly from the DB var pagedProducts = await _context.SellableProducts .Where(p => p.CategoryId == categoryId) .Include(p => p.Names) .Include(p => p.Descriptions) .Include(p => p.Image) // Included Image here .OrderBy(p => p.Id) .Skip(skip) .Take(effectiveSize) .ToListAsync(); // 3. Fetch Paged Customizable Products directly from the DB var pagedCustomizable = await _context.CustomizableProducts .Where(cp => cp.CategoryId == categoryId) .Include(cp => cp.Names) .Include(cp => cp.Descriptions) .Include(cp => cp.Image) // Included Image here .OrderBy(cp => cp.Id) .Skip(skip) .Take(effectiveSize) .ToListAsync(); // 4. Get total counts for the PagedResult metadata int totalProducts = await _context.SellableProducts.CountAsync(p => p.CategoryId == categoryId); int totalCustomizable = await _context.CustomizableProducts.CountAsync(cp => cp.CategoryId == categoryId); int totalCount = Math.Max(totalProducts, totalCustomizable); var data = new CategoryProductsData { Products = pagedProducts, CustomizableProducts = pagedCustomizable }; return new PagedResult( new List { data }, totalCount, page, effectiveSize ); } }