using Microsoft.EntityFrameworkCore;

namespace ZB.MOM.WW.OtOpcUa.Configuration.Validation;

/// <summary>
///     Materialises a <see cref="DraftSnapshot"/> from the live config DB so
///     <see cref="DraftValidator"/> can run against the current edit state at deploy time.
/// </summary>
/// <remarks>
///     <para>
///         This is a whole-DB ("global") snapshot — every cluster's rows in one pass — which is
///         what the deploy path needs: the admin-operations actor snapshots and flattens the
///         entire config, not one cluster. The validator's rules compare entity-level
///         <c>ClusterId</c> fields against each other (e.g. namespace binding), so the snapshot's
///         own <see cref="DraftSnapshot.ClusterId"/> is not read by any rule and is left empty.
///     </para>
///     <para>
///         <see cref="DraftSnapshot.GenerationId"/> is a placeholder (the generation model was
///         dropped); no rule reads it. <see cref="DraftSnapshot.PriorEquipment"/> is empty because
///         there is no prior-generation table to diff against. <see cref="DraftSnapshot.Enterprise"/>
///         / <see cref="DraftSnapshot.Site"/> are left null so the path-length rule uses its
///         conservative upper bound.
///     </para>
/// </remarks>
public static class DraftSnapshotFactory
{
    /// <summary>Builds a <see cref="DraftSnapshot"/> from the current config DB rows.</summary>
    /// <param name="db">The config DB context to read from.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A snapshot populated from the live DB, ready for <see cref="DraftValidator.Validate"/>.</returns>
    public static async Task<DraftSnapshot> FromConfigDbAsync(OtOpcUaConfigDbContext db, CancellationToken ct = default)
        => new DraftSnapshot
        {
            // GenerationId=0 and null Enterprise/Site are intentional conservative fallbacks at the
            // global-factory level: the path-length validator uses its upper bound, and cross-gen
            // EquipmentUuid checks run in separate validator passes — no rule here reads these fields.
            GenerationId = 0,            // generation model dropped; placeholder (no rule reads it)
            ClusterId = string.Empty,    // global snapshot; rules compare entity ClusterId fields, not this
            // v3: Namespace entity retired — no namespace list to materialize. The Raw tree
            // (RawFolders/TagGroups) + UnsTagReferences feed the v3 rules (charset, tagname length,
            // effective-leaf uniqueness).
            RawFolders         = await db.RawFolders.AsNoTracking().ToListAsync(ct),
            DriverInstances    = await db.DriverInstances.AsNoTracking().ToListAsync(ct),
            Devices            = await db.Devices.AsNoTracking().ToListAsync(ct),
            TagGroups          = await db.TagGroups.AsNoTracking().ToListAsync(ct),
            UnsAreas           = await db.UnsAreas.AsNoTracking().ToListAsync(ct),
            UnsLines           = await db.UnsLines.AsNoTracking().ToListAsync(ct),
            Equipment          = await db.Equipment.AsNoTracking().ToListAsync(ct),
            Tags               = await db.Tags.AsNoTracking().ToListAsync(ct),
            UnsTagReferences   = await db.UnsTagReferences.AsNoTracking().ToListAsync(ct),
            VirtualTags        = await db.VirtualTags.AsNoTracking().ToListAsync(ct),
            ScriptedAlarms     = await db.ScriptedAlarms.AsNoTracking().ToListAsync(ct),
            Scripts            = await db.Scripts.AsNoTracking().ToListAsync(ct),
            PollGroups         = await db.PollGroups.AsNoTracking().ToListAsync(ct),
            PriorEquipment     = [],   // intentional: no prior-generation table to diff against at this level
            ActiveReservations = await db.ExternalIdReservations
                .AsNoTracking()
                .Where(r => r.ReleasedAt == null)   // active only — matches DraftSnapshot.ActiveReservations semantics
                .ToListAsync(ct),
        };
}
