using FrameworkDAL.CustomCode.EIPConversation.EIPFlowAdmin; using FrameworkDAL.DTO.EIPConversation; 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 FrameworkBLL.EIPConversation.EIPAdmin.EIPFlowAdmin { public class EIPFlowAdminBLL : IEIPFlowAdminBLL { private static readonly Regex FlowCodePattern = new(@"^[A-Za-z0-9_]{1,50}$", RegexOptions.Compiled); private readonly IEIPFlowAdminDAL _dal; private readonly ILogger _logger; public EIPFlowAdminBLL( IEIPFlowAdminDAL dal, ILogger logger) { _dal = dal ?? throw new ArgumentNullException(nameof(dal)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } // ===================================================== // GET LIST // ===================================================== public async Task> GetFlowListAsync( byte? flowStatus, string? search, LoginDTO login, CancellationToken ct = default) { try { _logger.LogInformation( "BLL | GetFlowList | Tenant={TenantId} | FlowStatus={FlowStatus}", login.ClientId, flowStatus); return await _dal.GetFlowListAsync(flowStatus, search, login, ct) .ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "BLL | GetFlowList | Error | Tenant={TenantId}", login.ClientId); throw; } } // ===================================================== // GET SINGLE // ===================================================== public async Task GetFlowAsync( int flowDefinitionId, LoginDTO login, CancellationToken ct = default) { try { _logger.LogInformation( "BLL | GetFlow | Tenant={TenantId} | FlowDefinitionId={FlowDefinitionId}", login.ClientId, flowDefinitionId); return await _dal.GetFlowAsync(flowDefinitionId, login, ct) .ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "BLL | GetFlow | Error | FlowDefinitionId={FlowDefinitionId}", flowDefinitionId); throw; } } // ===================================================== // SAVE (INSERT or UPDATE) // ===================================================== public async Task SaveFlowAsync( EIPFlowAdminDTO dto, LoginDTO login, CancellationToken ct = default) { try { // ── Validate ────────────────────────────────────────── _logger.LogInformation( "BLL | SaveFlow | Validate | Tenant={TenantId} | FlowCode={FlowCode}", login.ClientId, dto.FlowCode); ValidateFlowCode(dto.FlowCode); ValidateJsonDefinition(dto.JsonDefinition); ValidateFlowStatus(dto.FlowStatus); // Duplicate FlowCode check bool codeExists = await _dal.FlowCodeExistsAsync( dto.FlowCode, login.ClientId, dto.FlowDefinitionId, login, ct).ConfigureAwait(false); if (codeExists) throw new InvalidOperationException( $"FlowCode '{dto.FlowCode}' already exists for this tenant."); dto.TenantId = login.ClientId; var isNew = dto.FlowDefinitionId == 0; // ── Persist ─────────────────────────────────────────── _logger.LogInformation( "BLL | SaveFlow | Persist | Tenant={TenantId} | FlowCode={FlowCode} | IsNew={IsNew}", login.ClientId, dto.FlowCode, isNew); if (isNew) { var newId = await _dal.InsertFlowAsync(dto, login, ct).ConfigureAwait(false); _logger.LogInformation( "BLL | SaveFlow | Inserted | NewId={NewId}", newId); return $"{SuccessResponse.SaveSuccessMessage} {newId}"; } else { await _dal.UpdateFlowAsync(dto, login, ct).ConfigureAwait(false); _logger.LogInformation( "BLL | SaveFlow | Updated | FlowDefinitionId={Id}", dto.FlowDefinitionId); return SuccessResponse.UpdateSuccess; } } catch (Exception ex) { _logger.LogError(ex, "BLL | SaveFlow | Error | Tenant={TenantId} | FlowCode={FlowCode}", login.ClientId, dto.FlowCode); throw; } } // ===================================================== // DELETE // ===================================================== public async Task DeleteFlowAsync( int flowDefinitionId, LoginDTO login, CancellationToken ct = default) { try { _logger.LogInformation( "BLL | DeleteFlow | Tenant={TenantId} | FlowDefinitionId={FlowDefinitionId}", login.ClientId, flowDefinitionId); // AutoNumber-generated ids in this repo are negative (e.g. -1210050000) — only 0 // (the C# default, meaning "never set") is genuinely invalid here. if (flowDefinitionId == 0) throw new ArgumentException("FlowDefinitionId must be a positive integer."); await _dal.DeleteFlowAsync(flowDefinitionId, login, ct).ConfigureAwait(false); _logger.LogInformation( "BLL | DeleteFlow | Completed | FlowDefinitionId={FlowDefinitionId}", flowDefinitionId); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { _logger.LogError(ex, "BLL | DeleteFlow | Error | FlowDefinitionId={FlowDefinitionId}", flowDefinitionId); throw; } } // ── Validation helpers ──────────────────────────────────────── private static void ValidateFlowCode(string flowCode) { if (string.IsNullOrWhiteSpace(flowCode)) throw new ArgumentException("FlowCode is required."); if (!FlowCodePattern.IsMatch(flowCode)) throw new ArgumentException( "FlowCode must contain only alphanumeric characters and underscores (max 50 chars)."); } private static void ValidateJsonDefinition(string jsonDefinition) { if (string.IsNullOrWhiteSpace(jsonDefinition)) throw new ArgumentException("JsonDefinition is required."); try { using var doc = System.Text.Json.JsonDocument.Parse(jsonDefinition); } catch (System.Text.Json.JsonException ex) { throw new ArgumentException($"JsonDefinition is not valid JSON: {ex.Message}", ex); } } private static void ValidateFlowStatus(byte flowStatus) { // 0=Draft, 1=Review, 2=Published, 3=Deprecated — matches CK_MEIPFLOWDEFINITION_FLOWSTATUS if (flowStatus > 3) throw new ArgumentException( "FlowStatus must be one of: 0=Draft, 1=Review, 2=Published, 3=Deprecated."); } } }