namespace ZB.MOM.WW.Overview.Registry;

/// <summary>
/// The dashboard's registry: which application instances to probe, and how often. Bound from the
/// <c>Overview</c> configuration section and validated by <see cref="OverviewOptionsValidator"/>.
/// </summary>
/// <remarks>
/// Every instance is probed the same way — GET <c>{BaseUrl}/health/ready</c>, plus
/// <c>{BaseUrl}/health/active</c> when <see cref="InstanceEntry.HasActiveRole"/>. There is
/// deliberately no per-instance probe-mode switch: one probe model, one status derivation.
/// </remarks>
public sealed class OverviewOptions
{
    /// <summary>The configuration section this binds to.</summary>
    public const string SectionName = "Overview";

    /// <summary>How often the poller sweeps every instance, in seconds.</summary>
    public int PollIntervalSeconds { get; set; } = 10;

    /// <summary>Per-request timeout, in seconds.</summary>
    public int TimeoutSeconds { get; set; } = 3;

    /// <summary>
    /// How old a snapshot may get before its instance renders as Stale (out of date / offline)
    /// regardless of its last known status, in seconds. This is what catches a stuck poller, a
    /// paused refresh or a slept machine — not just failed probes — so it must exceed the poll
    /// interval or every instance would read Stale between two healthy polls.
    /// </summary>
    public int StaleAfterSeconds { get; set; } = 45;

    /// <summary>The registered applications, each with one or more instances.</summary>
    public IList<ApplicationEntry> Applications { get; set; } = new List<ApplicationEntry>();
}

/// <summary>One registered application (a section on the dashboard).</summary>
public sealed class ApplicationEntry
{
    /// <summary>Display name, unique across the registry.</summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>Label for the per-instance management link, e.g. "AdminUI" or "Dashboard".</summary>
    public string ManagementLabel { get; set; } = "UI";

    /// <summary>Overrides <see cref="OverviewOptions.PollIntervalSeconds"/> for this application.</summary>
    public int? PollIntervalSeconds { get; set; }

    /// <summary>Overrides <see cref="OverviewOptions.TimeoutSeconds"/> for this application.</summary>
    public int? TimeoutSeconds { get; set; }

    /// <summary>Overrides <see cref="OverviewOptions.StaleAfterSeconds"/> for this application.</summary>
    public int? StaleAfterSeconds { get; set; }

    /// <summary>This application's instances; at least one is required.</summary>
    public IList<InstanceEntry> Instances { get; set; } = new List<InstanceEntry>();
}

/// <summary>One probed instance (a card on the dashboard).</summary>
public sealed class InstanceEntry
{
    /// <summary>Display name, unique within its application.</summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// The cluster group this instance belongs to (e.g. "central", "site-a"). One Akka cluster is
    /// one group: leader aggregation and the split-brain check are scoped to it. Null for
    /// standalone instances.
    /// </summary>
    public string? Group { get; set; }

    /// <summary>Absolute base URL whose <c>/health/*</c> endpoints are probed.</summary>
    public string BaseUrl { get; set; } = string.Empty;

    /// <summary>Absolute URL of this instance's own management UI, linked from its card.</summary>
    public string? ManagementUrl { get; set; }

    /// <summary>
    /// Whether this instance participates in an active/standby pair. False for the single-instance
    /// gateways, which have no such concept — their cards show status without an Active badge and
    /// are never probed for <c>/health/active</c>.
    /// </summary>
    public bool HasActiveRole { get; set; }

    /// <summary>Overrides the application and global poll interval for this instance.</summary>
    public int? PollIntervalSeconds { get; set; }

    /// <summary>Overrides the application and global request timeout for this instance.</summary>
    public int? TimeoutSeconds { get; set; }

    /// <summary>Overrides the application and global staleness window for this instance.</summary>
    public int? StaleAfterSeconds { get; set; }
}

/// <summary>
/// The effective (override-resolved) timings for one instance. Instance wins over application,
/// application wins over global — resolved once here so no caller re-implements the precedence.
/// </summary>
/// <param name="PollInterval">Effective poll cadence.</param>
/// <param name="Timeout">Effective per-request timeout.</param>
/// <param name="StaleAfter">Effective staleness window.</param>
public readonly record struct EffectiveTimings(TimeSpan PollInterval, TimeSpan Timeout, TimeSpan StaleAfter)
{
    /// <summary>Resolves the effective timings for <paramref name="instance"/>.</summary>
    /// <param name="options">The global registry defaults.</param>
    /// <param name="application">The instance's application.</param>
    /// <param name="instance">The instance itself.</param>
    /// <returns>The resolved timings.</returns>
    public static EffectiveTimings Resolve(
        OverviewOptions options,
        ApplicationEntry application,
        InstanceEntry instance)
    {
        ArgumentNullException.ThrowIfNull(options);
        ArgumentNullException.ThrowIfNull(application);
        ArgumentNullException.ThrowIfNull(instance);

        return new EffectiveTimings(
            TimeSpan.FromSeconds(
                instance.PollIntervalSeconds ?? application.PollIntervalSeconds ?? options.PollIntervalSeconds),
            TimeSpan.FromSeconds(
                instance.TimeoutSeconds ?? application.TimeoutSeconds ?? options.TimeoutSeconds),
            TimeSpan.FromSeconds(
                instance.StaleAfterSeconds ?? application.StaleAfterSeconds ?? options.StaleAfterSeconds));
    }
}
