using FlatRender.IdentitySvc.Application.Services.Interfaces; using FlatRender.IdentitySvc.Domain.Entities; using FlatRender.IdentitySvc.Domain.Enums; using FlatRender.IdentitySvc.Infrastructure.Data; using FlatRender.IdentitySvc.Models.Requests; using FlatRender.IdentitySvc.Models.Responses; using Microsoft.EntityFrameworkCore; namespace FlatRender.IdentitySvc.Application.Services; public class DiscountService(IdentityDbContext db) : IDiscountService { public async Task ValidateAsync(Guid tenantId, string code, Guid? planId) { var discount = await db.Discounts.FirstOrDefaultAsync(d => d.TenantId == tenantId && d.Code == code && d.IsActive && (d.StartsAt == null || d.StartsAt <= DateTime.UtcNow) && (d.ExpiresAt == null || d.ExpiresAt >= DateTime.UtcNow) && (d.MaxUseCount == null || d.UsedCount < d.MaxUseCount)); if (discount == null) return new DiscountValidateResponse(false, 0, "Unknown", 0); if (planId.HasValue && discount.AppliesToPlanIds != null && discount.AppliesToPlanIds.Length > 0) { if (!discount.AppliesToPlanIds.Contains(planId.Value)) return new DiscountValidateResponse(false, 0, discount.Kind.ToString(), discount.Value); } long discountMinor = 0; if (planId.HasValue) { var plan = await db.Plans.FindAsync(planId.Value); if (plan != null) { discountMinor = discount.Kind == DiscountKind.Percentage ? (long)(plan.PriceMinor * (double)discount.Value / 100) : (long)discount.Value; } } return new DiscountValidateResponse(true, discountMinor, discount.Kind.ToString(), discount.Value); } public async Task> ListAsync(Guid tenantId, int page, int pageSize) { var total = await db.Discounts.LongCountAsync(d => d.TenantId == tenantId); var discounts = await db.Discounts .Where(d => d.TenantId == tenantId) .OrderByDescending(d => d.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return new PagedResponse( discounts.Select(MapResponse).ToList(), new PaginationMeta(page, pageSize, total, total > (long)page * pageSize) ); } public async Task CreateAsync(Guid tenantId, CreateDiscountRequest request) { var exists = await db.Discounts.AnyAsync(d => d.TenantId == tenantId && d.Code == request.Code); if (exists) throw new InvalidOperationException("Discount code already exists"); if (!Enum.TryParse(request.Kind, true, out var kind)) throw new ArgumentException("Invalid discount kind"); var discount = new Discount { TenantId = tenantId, Name = request.Name, Code = request.Code.ToUpper(), Kind = kind, Value = request.Value, OwnerUserId = request.OwnerUserId, OwnerProfitPercentage = request.OwnerProfitPercentage, MaxUseCount = request.MaxUseCount, AppliesToPlanIds = request.AppliesToPlanIds, StartsAt = request.StartsAt, ExpiresAt = request.ExpiresAt, }; db.Discounts.Add(discount); await db.SaveChangesAsync(); return MapResponse(discount); } public async Task UpdateAsync(Guid tenantId, Guid id, UpdateDiscountRequest request) { var discount = await db.Discounts.FirstOrDefaultAsync(d => d.Id == id && d.TenantId == tenantId); if (discount == null) return null; if (request.Name != null) discount.Name = request.Name; if (request.Code != null) { var newCode = request.Code.ToUpper(); if (newCode != discount.Code) { var clash = await db.Discounts.AnyAsync(d => d.TenantId == tenantId && d.Code == newCode && d.Id != id); if (clash) throw new InvalidOperationException("Discount code already exists"); discount.Code = newCode; } } if (request.Kind != null) { if (!Enum.TryParse(request.Kind, true, out var kind)) throw new ArgumentException("Invalid discount kind"); discount.Kind = kind; } if (request.Value.HasValue) discount.Value = request.Value.Value; if (request.OwnerUserId.HasValue) discount.OwnerUserId = request.OwnerUserId; if (request.OwnerProfitPercentage.HasValue) discount.OwnerProfitPercentage = request.OwnerProfitPercentage.Value; if (request.MaxUseCount.HasValue) discount.MaxUseCount = request.MaxUseCount; if (request.AppliesToPlanIds != null) discount.AppliesToPlanIds = request.AppliesToPlanIds; if (request.StartsAt.HasValue) discount.StartsAt = request.StartsAt; if (request.ExpiresAt.HasValue) discount.ExpiresAt = request.ExpiresAt; if (request.IsActive.HasValue) discount.IsActive = request.IsActive.Value; discount.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(); return MapResponse(discount); } public async Task DeleteAsync(Guid tenantId, Guid id) { var discount = await db.Discounts.FirstOrDefaultAsync(d => d.Id == id && d.TenantId == tenantId); if (discount == null) return false; db.Discounts.Remove(discount); await db.SaveChangesAsync(); return true; } private static DiscountResponse MapResponse(Discount d) => new( d.Id, d.Name, d.Code, d.Kind.ToString(), d.Value, d.UsedCount, d.MaxUseCount, d.IsActive, d.ExpiresAt, d.CreatedAt ); }