using LiteCharms.Features.Extensions; using LiteCharms.Features.Models; using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.Features.Shop.Postgres; using static LiteCharms.Features.Extensions.Timezones; namespace LiteCharms.Features.Shop.CartPackages; public class PackageService(IDbContextFactory contextFactory) { public async ValueTask> AddPackageItemAsync(Guid packageId, Guid productPriceId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken)) return Result.Fail($"Could not find package by ID {packageId}"); if (!await context.ProductPrices.AnyAsync(p => p.Id == productPriceId && p.Active == true, cancellationToken)) return Result.Fail($"Could not find an active product price by ID {productPriceId}"); if (await context.PackageItems.AnyAsync(p => p.ProductPriceId == productPriceId && p.PackageId == packageId, cancellationToken)) return Result.Fail($"Product price {productPriceId} is already added to this package {packageId}"); var newPackageItem = context.PackageItems.Add(new Entities.PackageItem { PackageId = packageId, ProductPriceId = productPriceId, Active = true }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(newPackageItem.Entity.Id) : Result.Fail($"Failed to add new package item by ID {productPriceId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> CreatePackageAsync(string? name, string? summary, string? description, string? ImageUrl, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (await context.Packages.AnyAsync(p => p.Name == name, cancellationToken)) return Result.Fail($"A package by the same name already exists: {name}"); var newPackage = context.Packages.Add(new Entities.Package { Name = name, Summary = summary, Description = description, ImageUrl = ImageUrl, Active = true }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(newPackage.Entity.Id) : Result.Fail($"Failed to create a new package by the name: {name}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask DeletePackageItemAsync(Guid packageId, Guid packageItemId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken)) return Result.Fail($"Could not find package by ID {packageId}"); var item = await context.PackageItems.FirstOrDefaultAsync(p => p.Id == packageItemId && p.PackageId == packageId, cancellationToken); if (item is null) return Result.Fail($"Product item {packageItemId} is already added to this package {packageId}"); context.PackageItems.Remove(item); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail($"Failed to delete package item by id {packageItemId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask DeletePackageItemsAsync(Guid packageId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken)) return Result.Fail($"Could not find package by ID {packageId}"); var items = await context.PackageItems.Where(i => i.PackageId == packageId).ToArrayAsync(cancellationToken); context.PackageItems.RemoveRange(items); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail($"Failed to delete package {packageId} items"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetPackageAsync(Guid packageId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == packageId, cancellationToken); return package is not null ? Result.Ok(package.ToModel()) : Result.Fail($"Failed to find package by ID {packageId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetPackageItemsAsync(Guid packageId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken)) return Result.Fail($"Package could not be found with ID {packageId}"); var items = await context.PackageItems.AsNoTracking() .OrderByDescending(o => o.CreatedAt) .Where(p => p.PackageId == packageId) .ToArrayAsync(cancellationToken); return items?.Length > 0 ? Result.Ok(items.Select(i => i.ToModel()).ToArray()) : Result.Fail($"Could not find package items by package ID {packageId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetPackagesAsync(Guid packageId, DateRange range, bool active, CancellationToken cancellationToken = default) { try { var fromDate = range.From.ToDateTime(TimeOnly.MinValue); var toDate = range.To.ToDateTime(TimeOnly.MaxValue); using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var packages = await context.Packages .AsNoTracking() .OrderByDescending(o => o.CreatedAt) .Where(p => p.CreatedAt >= fromDate && p.CreatedAt <= toDate) .Where(p => p.Active == active) .Take(range.MaxRecords) .ToArrayAsync(cancellationToken); return packages?.Length > 0 ? Result.Ok(packages.Select(o => o.ToModel()).ToArray()) : Result.Fail(new Error($"No packages found for the specified date range {range.From} - {range.To}.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> UpdatePackageAsync(Guid packageId, string? name, string? summary, string? description, string? ImageUrl, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (await context.Packages.AnyAsync(p => p.Name == name, cancellationToken)) return Result.Fail($"A package by the same name already exists: {name}"); var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == packageId, cancellationToken); if (package is null) return Result.Fail($"Could not find package by id {packageId}"); package.Name = name; package.Summary = summary; package.Description = description; package.ImageUrl = ImageUrl; package.UpdatedAt = DateTime.UtcNow; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail($"Failed to update package with id {packageId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> UpdatePackageStatusAsync(Guid packageId, bool active, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == packageId, cancellationToken); if (package is null) return Result.Fail($"Could not find package by id {packageId}"); package.Active = active; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail($"Failed to update package with id {packageId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } }