using ZB.MOM.WW.OtOpcUa.Configuration.Enums;

namespace ZB.MOM.WW.OtOpcUa.Server.Redundancy;

/// <summary>
///     Snapshot of the cluster topology the <see cref="RedundancyCoordinator"/> holds. Read
///     once at startup + refreshed on publish-generation notification. Immutable — every
///     refresh produces a new instance so observers can compare identity-equality to detect
///     topology change.
/// </summary>
/// <remarks>
///     Per Phase 6.3 Stream A.1. Invariants enforced by the loader (see
///     <see cref="ClusterTopologyLoader"/>): at most one Primary per cluster for
///     WarmActive/Hot redundancy modes; every node has a unique ApplicationUri (OPC UA
///     Part 4 requirement — clients pin trust here); at most 2 nodes total per cluster
///     (decision #83).
/// </remarks>
public sealed record RedundancyTopology(
    string ClusterId,
    string SelfNodeId,
    RedundancyRole SelfRole,
    RedundancyMode Mode,
    IReadOnlyList<RedundancyPeer> Peers,
    string SelfApplicationUri)
{
    /// <summary>Peer count — 0 for a standalone (single-node) cluster, 1 for v2 two-node clusters.</summary>
    public int PeerCount => Peers.Count;

    /// <summary>
    ///     ServerUriArray shape per OPC UA Part 4 §6.6.2.2 — self first, peers in stable
    ///     deterministic order (lexicographic by NodeId), self's ApplicationUri always at index 0.
    /// </summary>
    public IReadOnlyList<string> ServerUriArray() =>
        new[] { SelfApplicationUri }
            .Concat(Peers.OrderBy(p => p.NodeId, StringComparer.OrdinalIgnoreCase).Select(p => p.ApplicationUri))
            .ToList();
}

/// <summary>One peer in the cluster (every node other than self).</summary>
/// <param name="NodeId">Peer's stable logical NodeId (e.g. <c>"LINE3-OPCUA-B"</c>).</param>
/// <param name="Role">Peer's declared redundancy role from the shared config DB.</param>
/// <param name="Host">Peer's hostname / IP — drives the health-probe target.</param>
/// <param name="OpcUaPort">Peer's OPC UA endpoint port.</param>
/// <param name="DashboardPort">Peer's dashboard / health-endpoint port.</param>
/// <param name="ApplicationUri">Peer's declared ApplicationUri (carried in <see cref="RedundancyTopology.ServerUriArray"/>).</param>
public sealed record RedundancyPeer(
    string NodeId,
    RedundancyRole Role,
    string Host,
    int OpcUaPort,
    int DashboardPort,
    string ApplicationUri);

/// <summary>Thrown when the loader detects a topology-invariant violation at startup or refresh.</summary>
public sealed class InvalidTopologyException(string message) : Exception(message);
