// File: EIPFlowRepository.cs using FrameworkDAL.CustomCode.EIPConversation.EIPFlow; using FrameworkDAL.DTO.EIPConversation; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; using Newtonsoft.Json; namespace FrameworkBLL.EIPConversation.EIPHandlers.EIPFlowRepository { public class EIPFlowRepository : IEIPFlowRepository { private readonly IEIPFlowDAL _flowDAL; private readonly ITenantContext _tenantContext; private readonly ILogger _logger; public EIPFlowRepository( IEIPFlowDAL flowDAL, ITenantContext tenantContext, ILogger logger) { _flowDAL = flowDAL ?? throw new ArgumentNullException(nameof(flowDAL)); _tenantContext = tenantContext ?? throw new ArgumentNullException(nameof(tenantContext)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task GetFlowDefinitionAsync( string flowCode, LoginDTO? login = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(flowCode)) throw new ArgumentException("FlowCode must be provided.", nameof(flowCode)); try { LoginDTO actualLogin = login ?? _tenantContext.LoginDTO; string tenantId = actualLogin.ClientId.ToString(); _logger.LogInformation( "Fetching FlowDefinition | FlowCode={FlowCode} | TenantId={TenantId}", flowCode, tenantId); var dalFlow = await _flowDAL.GetFlowDefinitionAsync( tenantId, flowCode, actualLogin); if (dalFlow == null) throw new InvalidOperationException( $"FlowDefinition not found for FlowCode={flowCode} and TenantId={tenantId}"); // ========================================================= // 🔥 JSON FALLBACK (CRITICAL FOR DYNAMIC SYSTEM) // ========================================================= if ((dalFlow.Steps == null || dalFlow.Steps.Count == 0) && !string.IsNullOrWhiteSpace(dalFlow.JsonDefinition)) { _logger.LogWarning( "Steps missing. Rehydrating from JsonDefinition | FlowCode={FlowCode}", flowCode); dalFlow.Steps = DeserializeStepsFromJson(dalFlow.JsonDefinition); } if (string.IsNullOrWhiteSpace(dalFlow.StartStepCode)) throw new InvalidOperationException( $"FlowDefinition must have StartStepCode defined. FlowCode={flowCode}"); if (dalFlow.Steps == null || dalFlow.Steps.Count == 0) throw new InvalidOperationException( $"FlowDefinition must contain steps. FlowCode={flowCode}"); // ========================================================= // 🔥 VALIDATION + NORMALIZATION // ========================================================= foreach (var kvp in dalFlow.Steps) { var stepCode = kvp.Key; var step = kvp.Value; if (string.IsNullOrWhiteSpace(stepCode)) throw new InvalidOperationException( $"StepCode key cannot be null or empty in FlowCode={flowCode}"); if (step == null) throw new InvalidOperationException( $"Step definition missing for StepCode={stepCode}"); if (string.IsNullOrWhiteSpace(step.StepType)) throw new InvalidOperationException( $"StepType must be defined for StepCode={stepCode}"); // 🔥 Normalize StepType once (avoid repeated ToUpper in engine) step.StepType = step.StepType.Trim().ToUpperInvariant(); // 🔥 Safe defaults (avoid null crashes later) step.DecisionMap ??= new Dictionary(); step.Metadata ??= new Dictionary(); step.Actions ??= new List(); } // ========================================================= // 🔥 START STEP VALIDATION // ========================================================= if (!dalFlow.Steps.ContainsKey(dalFlow.StartStepCode)) throw new InvalidOperationException( $"StartStepCode '{dalFlow.StartStepCode}' not found in steps."); // ========================================================= // 🔥 FINAL MAPPING (NO TRANSFORMATION) // ========================================================= var flowDefinition = new EIPFlowDefinitionDTO { FlowCode = dalFlow.FlowCode, StartStepCode = dalFlow.StartStepCode, Steps = dalFlow.Steps }; _logger.LogInformation( "FlowDefinition mapped successfully | FlowCode={FlowCode} | TenantId={TenantId} | StepsCount={StepsCount} | StartStepCode={StartStepCode}", flowDefinition.FlowCode, tenantId, flowDefinition.Steps.Count, flowDefinition.StartStepCode); return flowDefinition; } catch (Exception ex) { _logger.LogError(ex, "Error fetching flow definition | FlowCode={FlowCode}", flowCode); throw; } } // ========================================================= // 🔥 JSON DESERIALIZER // ========================================================= /// /// Converts JsonDefinition into Steps dictionary. /// Used as fallback when DB steps are missing. /// private Dictionary DeserializeStepsFromJson(string json) { try { var flow = JsonConvert.DeserializeObject(json); if (flow?.Steps == null || flow.Steps.Count == 0) throw new InvalidOperationException("Invalid JSON flow definition."); return flow.Steps; } catch (Exception ex) { _logger.LogError(ex, "Failed to deserialize JsonDefinition"); throw; } } } }