using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using PromotionExchangeDAL.CustomCode; using PromotionExchangeDAL.DTO; namespace PromotionExchangeBLL; public class PromotionExchangeBLL : IPromotionExchangeBLL { private readonly IExchangePartyDAL _PartyDal; private readonly IExchangePackageDAL _PackageDal; public PromotionExchangeBLL(IExchangePartyDAL partyDal, IExchangePackageDAL packageDal) { _PartyDal = partyDal; _PackageDal = packageDal; } public async Task UploadPackageAsync( string originApiKey, string destinationEnvironmentCode, byte[] packageBytes, CancellationToken ct) { if (packageBytes is null || packageBytes.Length == 0) throw new ArgumentException("Package is empty.", nameof(packageBytes)); var origin = await ResolvePartyByApiKey(originApiKey, ct).ConfigureAwait(false); var destination = await _PartyDal.GetByEnvironmentCode(destinationEnvironmentCode, ct).ConfigureAwait(false) ?? throw new InvalidOperationException( $"'{destinationEnvironmentCode}' is not a registered Exchange party — cannot upload to it."); var dto = new ExchangePackageDTO { OriginEnvironmentCode = origin.EnvironmentCode, DestinationEnvironmentCode = destination.EnvironmentCode, PackageBytes = packageBytes, PackageSizeBytes = packageBytes.Length }; return await _PackageDal.Insert(dto, ct).ConfigureAwait(false); } public async Task> ListPendingAsync( string destinationApiKey, CancellationToken ct) { var destination = await ResolvePartyByApiKey(destinationApiKey, ct).ConfigureAwait(false); return await _PackageDal.GetPendingForDestination(destination.EnvironmentCode, ct).ConfigureAwait(false); } public async Task DownloadPackageAsync( string destinationApiKey, int packageId, CancellationToken ct) { var destination = await ResolvePartyByApiKey(destinationApiKey, ct).ConfigureAwait(false); var package = await _PackageDal.GetByIdForDestination(packageId, destination.EnvironmentCode, ct) .ConfigureAwait(false) ?? throw new InvalidOperationException( $"Package {packageId} was not found addressed to '{destination.EnvironmentCode}'."); await _PackageDal.MarkDownloaded(packageId, ct).ConfigureAwait(false); return package; } private async Task ResolvePartyByApiKey(string apiKey, CancellationToken ct) { if (string.IsNullOrWhiteSpace(apiKey)) throw new UnauthorizedAccessException("API key is required."); var hash = HashApiKey(apiKey); return await _PartyDal.GetByApiKeyHash(hash, ct).ConfigureAwait(false) ?? throw new UnauthorizedAccessException("API key is not recognized or is no longer active."); } // Same SHA-256 hex convention as Partner's ApiKeyAuthMiddleware (X-Api-Key hashed before // lookup) — never stores or compares the raw key. private static string HashApiKey(string apiKey) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(apiKey)); return Convert.ToHexString(bytes).ToLowerInvariant(); } }