using EAIAdminDAL.CustomCode;
using EAIAdminDAL.DTO;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.Resource.Response;
using KmsDAL.CustomCode.KmDomain;
using KmsDAL.CustomCode.KmSpace;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace EAIAdminBLL.EAIBotKnowledgeSource
{
///
/// Cross-module BLL — EAIAdmin and KMS both live in PlatformHost's process (see the approved
/// plan's "Module placement — resolved" section), so this can make in-process DAL calls into
/// KmsDAL's IKmDomainDAL/IKmSpaceDAL directly. Tenant-consistency validation is free: both
/// GetKmDomain/GetKmSpace already filter by TENANTID in their own WHERE clause (see
/// KmDomainQB.GET_KMDOMAIN/KmSpaceQB.GET_KMSPACE), so calling them with the requester's own
/// login and getting nothing back means the target either doesn't exist or belongs to another
/// tenant — same rejection either way, no separate TenantId-equality check needed.
///
public class EAIBotKnowledgeSourceBLL : IEAIBotKnowledgeSourceBLL
{
private readonly IEAIBotKnowledgeSourceDAL _dal;
private readonly IEAIBotAdminDAL _botDal;
private readonly IEAICapabilityAdminDAL _capabilityDal;
private readonly IKmDomainDAL _kmDomainDal;
private readonly IKmSpaceDAL _kmSpaceDal;
private readonly ILogger _logger;
public EAIBotKnowledgeSourceBLL(
IEAIBotKnowledgeSourceDAL dal,
IEAIBotAdminDAL botDal,
IEAICapabilityAdminDAL capabilityDal,
IKmDomainDAL kmDomainDal,
IKmSpaceDAL kmSpaceDal,
ILogger logger)
{
_dal = dal ?? throw new ArgumentNullException(nameof(dal));
_botDal = botDal ?? throw new ArgumentNullException(nameof(botDal));
_capabilityDal = capabilityDal ?? throw new ArgumentNullException(nameof(capabilityDal));
_kmDomainDal = kmDomainDal ?? throw new ArgumentNullException(nameof(kmDomainDal));
_kmSpaceDal = kmSpaceDal ?? throw new ArgumentNullException(nameof(kmSpaceDal));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task> GetListByBotAsync(
int eaiBotId, LoginDTO login, CancellationToken ct = default)
=> await _dal.GetListByBotAsync(eaiBotId, login, ct).ConfigureAwait(false);
public async Task> GetListByCapabilityAsync(
int eaiCapabilityId, LoginDTO login, CancellationToken ct = default)
=> await _dal.GetListByCapabilityAsync(eaiCapabilityId, login, ct).ConfigureAwait(false);
public async Task SaveLinkAsync(
EAIBotKnowledgeSourceDTO dto, LoginDTO login, CancellationToken ct = default)
{
try
{
bool botSet = dto.EAIBotId > 0;
bool capSet = dto.EAICapabilityId > 0;
if (botSet == capSet) // both set or neither set
throw new ArgumentException("Exactly one of EAIBotId/EAICapabilityId must be set.");
bool spaceSet = dto.KmSpaceId > 0;
bool domainSet = dto.KmDomainId > 0;
if (spaceSet == domainSet)
throw new ArgumentException("Exactly one of KmSpaceId/KmDomainId must be set.");
// Never allow cross-tenant linkage — enforced exactly once, at save time, in the
// one BLL method that can create this row.
if (botSet && await _botDal.GetBotAsync(dto.EAIBotId, login, ct).ConfigureAwait(false) is null)
throw new InvalidOperationException($"Bot {dto.EAIBotId} not found for this tenant.");
if (capSet && await _capabilityDal.GetCapabilityAsync(dto.EAICapabilityId, login, ct).ConfigureAwait(false) is null)
throw new InvalidOperationException($"Capability {dto.EAICapabilityId} not found for this tenant.");
if (spaceSet && string.IsNullOrWhiteSpace(await _kmSpaceDal.GetKmSpace(dto.KmSpaceId, login, ct).ConfigureAwait(false)))
throw new InvalidOperationException($"KmSpace {dto.KmSpaceId} not found for this tenant.");
if (domainSet && string.IsNullOrWhiteSpace(await _kmDomainDal.GetKmDomain(dto.KmDomainId, login, ct).ConfigureAwait(false)))
throw new InvalidOperationException($"KmDomain {dto.KmDomainId} not found for this tenant.");
var newId = await _dal.InsertLinkAsync(dto, login, ct).ConfigureAwait(false);
_logger.LogInformation("BLL | SaveLink | Completed | NewId={NewId}", newId);
return $"{SuccessResponse.SaveSuccessMessage} {newId}";
}
catch (Exception ex)
{
_logger.LogError(ex, "BLL | SaveLink | Error | BotId={BotId} CapabilityId={CapabilityId}",
dto.EAIBotId, dto.EAICapabilityId);
throw;
}
}
public async Task DeleteLinkAsync(
int eaiBotKnowledgeSourceId, LoginDTO login, CancellationToken ct = default)
{
if (eaiBotKnowledgeSourceId == 0)
throw new ArgumentException("EAIBotKnowledgeSourceId must be a positive integer.");
await _dal.DeleteLinkAsync(eaiBotKnowledgeSourceId, login, ct).ConfigureAwait(false);
return SuccessResponse.DeleteSuccessMessage;
}
}
}