using System;
using System.Threading;
using System.Threading.Tasks;
using FrameworkBLL.Page;
using FrameworkDAL.CustomCode.Promotion;
using FrameworkDAL.DTO.Page;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.DTO.Promotion;
using GB5Shared.Promotion;
using Newtonsoft.Json;
namespace FrameworkBLL.Promotion.Adapters
{
// ============================================================
// PagePromotionAdapter — IPromotableEntity for MPAGE ("PAGE"). Parent row
// only — MPAGE has no child/detail rows of its own (Dashboard->Page
// placement lives on the Dashboard side, in MDASHBOARDPAGE, not here).
//
// PageCode is the natural key, resolved via the new IPageCodeLookupDAL —
// this is also what unblocks DashboardPromotionAdapter's own PageId
// references (MDASHBOARDPAGE rows) and ReportView's own PageId (still
// blocked there pending this adapter's registration).
//
// ModuleId/ModuleCode reuses the existing IModuleCodeLookupDAL (module
// codes are static/shared across installs, low risk, same reasoning as
// ReportView/DrillDown's own Module reference).
//
// DefaultCriteriaConfigId and ImageId are raw AutoNumber-ID references
// with no natural-key lookup built yet — conservatively BLOCKED if set to
// a real (non-sentinel) value, same "carry it in Flags for validation to
// see, never map it at import" pattern as ReportView's own risky fields.
// ============================================================
public class PagePromotionAdapter : IPromotableEntity
{
public string EntityTypeCode => "PAGE";
private readonly IPageBLL _PageBLL;
private readonly IPageCodeLookupDAL _PageCodeLookup;
private readonly IModuleCodeLookupDAL _ModuleCodeLookup;
public PagePromotionAdapter(
IPageBLL pageBLL, IPageCodeLookupDAL pageCodeLookup, IModuleCodeLookupDAL moduleCodeLookup)
{
_PageBLL = pageBLL;
_PageCodeLookup = pageCodeLookup;
_ModuleCodeLookup = moduleCodeLookup;
}
private class RawFlags
{
public int DefaultCriteriaConfigId { get; set; }
public int ImageId { get; set; }
}
private class PortablePayload
{
public string PageCode { get; set; } = string.Empty;
public string PageName { get; set; } = string.Empty;
public string? PageRemarks { get; set; }
public string? PageLayOut { get; set; }
public byte PageType { get; set; }
public byte OverrideDashboardCriteria { get; set; }
public string? ModuleCode { get; set; }
public string? ImageLinkURL { get; set; }
public short PageSortOrder { get; set; }
/// Seen only by ValidateForPromotionAsync — ImportPromotedAsync never maps
/// these onto the destination row. See the class header comment.
public RawFlags Flags { get; set; } = new();
}
public async Task ExportForPromotionAsync(
int originEntityId, LoginDTO login, CancellationToken ct)
{
var json = await _PageBLL.GetPage(originEntityId, login).ConfigureAwait(false);
var dto = JsonConvert.DeserializeObject(json)
?? throw new InvalidOperationException($"Page {originEntityId} not found.");
string? moduleCode = null;
if (dto.ModuleId != 0 && dto.ModuleId != -1)
{
moduleCode = await _ModuleCodeLookup.GetModuleCodeById(dto.ModuleId, login, ct).ConfigureAwait(false)
?? throw new InvalidOperationException(
$"Page {originEntityId} references ModuleId {dto.ModuleId}, which has no ModuleCode — cannot promote.");
}
var payload = new PortablePayload
{
PageCode = dto.PageCode,
PageName = dto.PageName,
PageRemarks = dto.PageRemarks,
PageLayOut = dto.PageLayOut,
PageType = dto.PageType,
OverrideDashboardCriteria = dto.OverrideDashboardCriteria,
ModuleCode = moduleCode,
ImageLinkURL = dto.ImageLinkURL,
PageSortOrder = dto.PageSortOrder,
Flags = new RawFlags
{
DefaultCriteriaConfigId = dto.DefaultCriteriaConfigId,
ImageId = dto.ImageId
}
};
return new PromotionBundleDTO
{
EntityTypeCode = EntityTypeCode,
OriginEntityId = originEntityId,
PayloadJson = System.Text.Json.JsonSerializer.Serialize(payload)
};
}
public Task ValidateForPromotionAsync(
PromotionBundleDTO bundle, CancellationToken ct)
{
var payload = System.Text.Json.JsonSerializer.Deserialize(bundle.PayloadJson);
if (payload is null)
return Task.FromResult(PromotionValidationResultDTO.Block("Page payload could not be parsed."));
var reasons = new System.Collections.Generic.List();
var flags = payload.Flags;
if (flags.DefaultCriteriaConfigId != 0 && flags.DefaultCriteriaConfigId != -1)
reasons.Add("Page.DefaultCriteriaConfigId is set — criteria-config remapping is not yet supported.");
if (flags.ImageId != 0 && flags.ImageId != -1)
reasons.Add("Page.ImageId is set — image remapping is not yet supported.");
var result = reasons.Count == 0
? PromotionValidationResultDTO.Ok()
: new PromotionValidationResultDTO { IsPromotable = false, BlockingReasons = reasons };
return Task.FromResult(result);
}
public async Task ImportPromotedAsync(
PromotionBundleDTO bundle, byte destinationSourceType, int? existingDestinationId,
LoginDTO login, CancellationToken ct)
{
var payload = System.Text.Json.JsonSerializer.Deserialize(bundle.PayloadJson)
?? throw new InvalidOperationException("Page promotion payload could not be parsed.");
var moduleId = -1;
if (!string.IsNullOrWhiteSpace(payload.ModuleCode))
{
moduleId = await _ModuleCodeLookup.GetModuleIdByCode(payload.ModuleCode, login, ct).ConfigureAwait(false)
?? throw new InvalidOperationException(
$"No Module with ModuleCode '{payload.ModuleCode}' exists at this destination.");
}
// If this is an update to a previously-promoted page, existingDestinationId already
// identifies it directly. Otherwise, check whether a page with this code already exists
// at the destination (created independently, not via a prior promotion) so Import
// updates it rather than violating PageCode's own unique constraint with a duplicate.
var resolvedId = existingDestinationId
?? await _PageCodeLookup.GetPageIdByCode(payload.PageCode, login, ct).ConfigureAwait(false)
?? 0;
var dto = new PageDTO
{
PageId = resolvedId,
PageCode = payload.PageCode,
PageName = payload.PageName,
PageRemarks = payload.PageRemarks,
PageLayOut = payload.PageLayOut,
PageType = payload.PageType,
OverrideDashboardCriteria = payload.OverrideDashboardCriteria,
ModuleId = moduleId,
ImageId = -1, // never mapped — see class header comment
ImageLinkURL = payload.ImageLinkURL,
PageSortOrder = payload.PageSortOrder,
PageSourceType = destinationSourceType,
DefaultCriteriaConfigId = -1 // never mapped — see class header comment
};
await _PageBLL.SavePage(dto, login).ConfigureAwait(false);
return dto.PageId; // mutated in place by SavePage when isNew
}
}
}