using EAIAdminBLL.Sync; using EAIAdminDAL.CustomCode; using EAIAdminDAL.DTO; using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace EAIAdminBLL.EAICapabilityAdmin { public class EAICapabilityAdminBLL : IEAICapabilityAdminBLL { private static readonly Regex CapabilityCodePattern = new(@"^[A-Za-z0-9_]{1,50}$", RegexOptions.Compiled); private readonly IEAICapabilityAdminDAL _dal; private readonly IEAISyncClient _syncClient; private readonly ILogger _logger; public EAICapabilityAdminBLL( IEAICapabilityAdminDAL dal, IEAISyncClient syncClient, ILogger logger) { _dal = dal ?? throw new ArgumentNullException(nameof(dal)); _syncClient = syncClient ?? throw new ArgumentNullException(nameof(syncClient)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task> GetCapabilityListAsync( string? search, LoginDTO login, CancellationToken ct = default) { try { return await _dal.GetCapabilityListAsync(search, login, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "BLL | GetCapabilityList | Error | Tenant={TenantId}", login.ClientId); throw; } } public async Task GetCapabilityAsync( int capabilityId, LoginDTO login, CancellationToken ct = default) { try { return await _dal.GetCapabilityAsync(capabilityId, login, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "BLL | GetCapability | Error | CapabilityId={Id}", capabilityId); throw; } } public async Task SaveCapabilityAsync( EAICapabilityAdminDTO dto, LoginDTO login, CancellationToken ct = default) { try { ValidateCapabilityCode(dto.CapabilityCode); if (string.IsNullOrWhiteSpace(dto.CapabilityName)) throw new ArgumentException("CapabilityName is required."); // CAPABILITYCODE is globally unique — matches gbEAI's unique=True on // aicapabilitycode, not per-tenant. bool codeExists = await _dal.CapabilityCodeExistsAsync( dto.CapabilityCode, dto.CapabilityId, login, ct).ConfigureAwait(false); if (codeExists) throw new InvalidOperationException( $"CapabilityCode '{dto.CapabilityCode}' already exists."); dto.TenantId = login.ClientId; var isNew = dto.CapabilityId == 0; if (isNew) { var newId = await _dal.InsertCapabilityAsync(dto, login, ct).ConfigureAwait(false); dto.CapabilityId = newId; } else { await _dal.UpdateCapabilityAsync(dto, login, ct).ConfigureAwait(false); } // Push to gbEAI AFTER the local commit — never inside a transaction. A failed push // does not roll back the local save; GB5 stays system-of-record. var syncResult = await _syncClient.PushCapabilityAsync(dto, login, ct).ConfigureAwait(false); if (syncResult.Success && syncResult.EAIRemoteId is { } remoteId) { await _dal.UpdateEAIRemoteIdAsync(dto.CapabilityId, remoteId, login, ct).ConfigureAwait(false); } var baseMessage = isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.CapabilityId}" : SuccessResponse.UpdateSuccess; return syncResult.Success ? baseMessage : $"{baseMessage} Sync to AI-Enterprise-v1.0 failed: {syncResult.Message} — retry from this screen."; } catch (Exception ex) { _logger.LogError(ex, "BLL | SaveCapability | Error | Tenant={TenantId} | CapabilityCode={Code}", login.ClientId, dto.CapabilityCode); throw; } } public async Task DeleteCapabilityAsync( int capabilityId, LoginDTO login, CancellationToken ct = default) { try { if (capabilityId == 0) throw new ArgumentException("CapabilityId must be a positive integer."); var existing = await _dal.GetCapabilityAsync(capabilityId, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException($"Capability {capabilityId} not found."); await _dal.DeleteCapabilityAsync(capabilityId, login, ct).ConfigureAwait(false); var syncResult = await _syncClient.DeleteCapabilityAsync(existing, login, ct).ConfigureAwait(false); return syncResult.Success ? SuccessResponse.DeleteSuccessMessage : $"{SuccessResponse.DeleteSuccessMessage} Sync to AI-Enterprise-v1.0 failed: {syncResult.Message} — retry from this screen."; } catch (Exception ex) { _logger.LogError(ex, "BLL | DeleteCapability | Error | CapabilityId={Id}", capabilityId); throw; } } private static void ValidateCapabilityCode(string capabilityCode) { if (string.IsNullOrWhiteSpace(capabilityCode)) throw new ArgumentException("CapabilityCode is required."); if (!CapabilityCodePattern.IsMatch(capabilityCode)) throw new ArgumentException( "CapabilityCode must contain only alphanumeric characters and underscores (max 50 chars)."); } } }