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.Threading; using System.Threading.Tasks; namespace EAIAdminBLL.EAIPromptTemplateAdmin { public class EAIPromptTemplateAdminBLL : IEAIPromptTemplateAdminBLL { private readonly IEAIPromptTemplateAdminDAL _dal; private readonly IEAISyncClient _syncClient; private readonly ILogger _logger; public EAIPromptTemplateAdminBLL( IEAIPromptTemplateAdminDAL 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> GetPromptTemplateListAsync( string? search, LoginDTO login, CancellationToken ct = default) { try { return await _dal.GetPromptTemplateListAsync(search, login, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "BLL | GetPromptTemplateList | Error | Tenant={TenantId}", login.ClientId); throw; } } public async Task GetPromptTemplateAsync( int promptId, LoginDTO login, CancellationToken ct = default) { try { return await _dal.GetPromptTemplateAsync(promptId, login, ct).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "BLL | GetPromptTemplate | Error | PromptId={Id}", promptId); throw; } } public async Task SavePromptTemplateAsync( EAIPromptTemplateAdminDTO dto, LoginDTO login, CancellationToken ct = default) { try { if (string.IsNullOrWhiteSpace(dto.InitiativeCode)) throw new ArgumentException("InitiativeCode is required."); if (string.IsNullOrWhiteSpace(dto.CapabilityCode)) throw new ArgumentException("CapabilityCode is required."); if (string.IsNullOrWhiteSpace(dto.PromptTemplate)) throw new ArgumentException("PromptTemplate is required."); // Uniqueness is per (TenantId, InitiativeCode, CapabilityCode, VariantCode) — NOT a // globally unique code column like Initiative's InitiativeCode. Empty string is // normalized to null so "" and NULL aren't treated as two different default rows. if (string.IsNullOrWhiteSpace(dto.VariantCode)) dto.VariantCode = null; bool exists = await _dal.ExistsAsync( dto.InitiativeCode, dto.CapabilityCode, dto.VariantCode, dto.PromptId, login, ct) .ConfigureAwait(false); if (exists) throw new InvalidOperationException( $"A prompt template for InitiativeCode '{dto.InitiativeCode}', CapabilityCode " + $"'{dto.CapabilityCode}', VariantCode '{dto.VariantCode ?? "(default)"}' already exists."); dto.TenantId = login.ClientId; // PromptVersion is read-only/server-computed — never accept it from the client. // The authoritative value is whatever gbEAI's sync response returns below; on // insert the DAL always seeds PROMPTVERSION = 1 regardless of dto.PromptVersion. var isNew = dto.PromptId == 0; if (isNew) { var newId = await _dal.InsertPromptTemplateAsync(dto, login, ct).ConfigureAwait(false); dto.PromptId = newId; } else { await _dal.UpdatePromptTemplateAsync(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.PushPromptTemplateAsync(dto, login, ct).ConfigureAwait(false); if (syncResult.Success && (syncResult.EAIRemoteId is not null || syncResult.PromptVersion is not null)) { await _dal.UpdateSyncResultAsync( dto.PromptId, syncResult.EAIRemoteId, syncResult.PromptVersion, login, ct).ConfigureAwait(false); } var baseMessage = isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.PromptId}" : 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 | SavePromptTemplate | Error | Tenant={TenantId} | {Initiative}/{Capability}", login.ClientId, dto.InitiativeCode, dto.CapabilityCode); throw; } } public async Task DeletePromptTemplateAsync( int promptId, LoginDTO login, CancellationToken ct = default) { try { if (promptId == 0) throw new ArgumentException("PromptId must be a positive integer."); var existing = await _dal.GetPromptTemplateAsync(promptId, login, ct).ConfigureAwait(false) ?? throw new InvalidOperationException($"PromptTemplate {promptId} not found."); await _dal.DeletePromptTemplateAsync(promptId, login, ct).ConfigureAwait(false); var syncResult = await _syncClient.DeletePromptTemplateAsync(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 | DeletePromptTemplate | Error | PromptId={Id}", promptId); throw; } } } }