using System; using System.Collections.Generic; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using FrameworkDAL.CustomCode.Promotion; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Promotion; using GB5Shared.Promotion; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace FrameworkBLL.Promotion { public class PromotionBLL : IPromotionBLL { private readonly IServiceProvider _ServiceProvider; private readonly IPromotionPackageCrypto _Crypto; private readonly IPromotableEntityTypeDAL _EntityTypeDal; private readonly ILogger _Logger; public PromotionBLL( IServiceProvider serviceProvider, IPromotionPackageCrypto crypto, IPromotableEntityTypeDAL entityTypeDal, ILogger logger) { _ServiceProvider = serviceProvider; _Crypto = crypto; _EntityTypeDal = entityTypeDal; _Logger = logger; } public async Task BuildExportBundleAsync( string entityTypeCode, int originEntityId, LoginDTO login, CancellationToken ct) { if (string.IsNullOrWhiteSpace(entityTypeCode)) throw new InvalidOperationException("EntityTypeCode is required."); var entity = _ServiceProvider.GetKeyedService(entityTypeCode) ?? throw new InvalidOperationException( $"No IPromotableEntity registered for EntityTypeCode '{entityTypeCode}'."); var bundle = await entity.ExportForPromotionAsync(originEntityId, login, ct).ConfigureAwait(false); var validation = await entity.ValidateForPromotionAsync(bundle, ct).ConfigureAwait(false); if (!validation.IsPromotable) { var reasons = string.Join("; ", validation.BlockingReasons); _Logger.LogWarning( "PromotionBLL: export blocked for {EntityTypeCode}/{OriginEntityId} — {Reasons}", entityTypeCode, originEntityId, reasons); throw new InvalidOperationException($"Not promotable: {reasons}"); } return bundle; } public async Task BuildPackageAsync( IReadOnlyList items, string destinationEnvironmentCode, LoginDTO login, CancellationToken ct) { if (items.Count == 0) throw new InvalidOperationException("A promotion package must contain at least one item."); var manifest = new PromotionPackageManifestDTO { OriginEnvironmentCode = items[0].OriginEnvironmentCode, DestinationEnvironmentCode = destinationEnvironmentCode, CreatedOn = DateTime.UtcNow, CreatedByUserId = login.UserId, Items = new List(items) }; var plaintext = JsonSerializer.SerializeToUtf8Bytes(manifest); return await _Crypto.EncryptPackageAsync(plaintext, destinationEnvironmentCode, login, ct) .ConfigureAwait(false); } public async Task OpenPackageAsync( byte[] encryptedPackage, CancellationToken ct) { var plaintext = await _Crypto.DecryptPackageAsync(encryptedPackage, ct).ConfigureAwait(false); return JsonSerializer.Deserialize(plaintext) ?? throw new InvalidOperationException("Promotion package manifest could not be parsed."); } public async Task GetSelectListPromotableEntityType(LoginDTO login, CancellationToken ct) { var list = await _EntityTypeDal.GetSelectList(login, ct).ConfigureAwait(false); return Newtonsoft.Json.JsonConvert.SerializeObject(list); } } }