namespace ZB.MOM.WW.ScadaBridge.LoadHarness;

/// <summary>
/// Scale and duration knobs for a load-harness run. Defaults are the Phase-8 WP-4
/// target scale (10 sites x 500 instances x 75 tags = 375,000 subscriptions).
/// Every value is overridable from the command line so the same binary serves both
/// the full-scale protocol run and the scaled-down CI smoke.
/// </summary>
public sealed record HarnessConfig
{
    /// <summary>Number of simulated sites (WP-4 acceptance criterion <c>[2.5-1]</c>).</summary>
    public int Sites { get; init; } = 10;

    /// <summary>Instance Actors per site (WP-4 <c>[2.5-2]</c>).</summary>
    public int InstancesPerSite { get; init; } = 500;

    /// <summary>Data-sourced attributes ("live tags") per instance (WP-4 <c>[2.5-3]</c>).</summary>
    public int TagsPerInstance { get; init; } = 75;

    /// <summary>
    /// Nominal per-tag update period. The driver emits each tag once per period, so
    /// the site-wide event rate is <c>InstancesPerSite * TagsPerInstance / period</c>.
    /// The 10 s default puts the target-scale fleet at 37,500 tag updates/second.
    /// </summary>
    public TimeSpan TagUpdatePeriod { get; init; } = TimeSpan.FromSeconds(10);

    /// <summary>
    /// Length of the steady-state measurement window, measured AFTER the ramp
    /// completes and after the post-ramp settle. Memory-growth and CPU figures are
    /// computed over exactly this window.
    /// </summary>
    public TimeSpan SustainDuration { get; init; } = TimeSpan.FromMinutes(20);

    /// <summary>
    /// Quiet period between the last instance starting and the start of the
    /// measurement window — lets startup allocations settle so the memory-growth
    /// slope reflects steady state, not the ramp.
    /// </summary>
    public TimeSpan SettleDuration { get; init; } = TimeSpan.FromMinutes(2);

    /// <summary>Resource-sampling cadence (working set, GC heap, CPU, thread count).</summary>
    public TimeSpan SampleInterval { get; init; } = TimeSpan.FromSeconds(10);

    /// <summary>
    /// Health report cadence. Defaults to the production
    /// <c>HealthMonitoringOptions.ReportInterval</c> (30 s) so the timing measured is
    /// the one that ships; only the CI smoke shortens it, because a 20-second smoke
    /// window would otherwise never see a single tick.
    /// </summary>
    public TimeSpan HealthReportInterval { get; init; } = TimeSpan.FromSeconds(30);

    /// <summary>How often to take a debug view snapshot of a random live instance.</summary>
    public TimeSpan DebugProbeInterval { get; init; } = TimeSpan.FromSeconds(5);

    /// <summary>
    /// Pause between the last Instance Actor starting and the tag driver starting, to
    /// let every instance complete its DCL subscribe round-trip. The adapter callback
    /// is captured at subscribe time, so an emit before that lands is silently
    /// discarded — this window is what keeps <c>SkippedNoCallback</c> at zero.
    /// </summary>
    public TimeSpan SubscribeSettleDuration { get; init; } = TimeSpan.FromSeconds(30);

    /// <summary>
    /// Instances per site that carry a live stream subscriber (a real
    /// <c>StreamRelayActor</c> + bounded DropOldest channel, i.e. the production
    /// Debug View / central shape). Every subscriber's stream graph sees the FULL
    /// site event flow and filters it, so this is the fan-out multiplier.
    /// </summary>
    public int StreamProbesPerSite { get; init; } = 10;

    /// <summary>Store-and-forward messages enqueued for the drain-rate measurement (register row 50).</summary>
    public int StoreAndForwardDrainMessages { get; init; } = 20_000;

    /// <summary>Events published at the slow-subscriber isolation probe (register row 50).</summary>
    public int SlowSubscriberEvents { get; init; } = 200_000;

    /// <summary>Directory for the site SQLite files. A temp directory is used when null.</summary>
    public string? DataDirectory { get; init; }

    /// <summary>Path the JSON metrics document is written to.</summary>
    public string ResultsPath { get; init; } = "loadharness-results.json";

    /// <summary>Total live tag subscriptions across the fleet.</summary>
    public int TotalSubscriptions => Sites * InstancesPerSite * TagsPerInstance;

    /// <summary>Nominal fleet-wide tag updates per second implied by the scale and update period.</summary>
    public double NominalUpdatesPerSecond => TotalSubscriptions / TagUpdatePeriod.TotalSeconds;

    /// <summary>
    /// Parses <c>--key value</c> / <c>--key=value</c> arguments over the defaults.
    /// Unknown keys throw so a typo in a 20-minute run's command line fails fast
    /// rather than silently measuring the wrong scale.
    /// </summary>
    /// <param name="args">Raw command-line arguments.</param>
    /// <returns>The parsed configuration.</returns>
    public static HarnessConfig Parse(string[] args)
    {
        var config = new HarnessConfig();
        for (var i = 0; i < args.Length; i++)
        {
            var arg = args[i];
            if (!arg.StartsWith("--", StringComparison.Ordinal))
                throw new ArgumentException($"Unexpected argument '{arg}' (expected --key value).");

            string key;
            string value;
            var eq = arg.IndexOf('=', StringComparison.Ordinal);
            if (eq >= 0)
            {
                key = arg[2..eq];
                value = arg[(eq + 1)..];
            }
            else
            {
                key = arg[2..];
                if (i + 1 >= args.Length)
                    throw new ArgumentException($"Option '--{key}' requires a value.");
                value = args[++i];
            }

            config = key switch
            {
                "sites" => config with { Sites = int.Parse(value) },
                "instances-per-site" => config with { InstancesPerSite = int.Parse(value) },
                "tags-per-instance" => config with { TagsPerInstance = int.Parse(value) },
                "tag-update-period-seconds" => config with { TagUpdatePeriod = TimeSpan.FromSeconds(double.Parse(value)) },
                "sustain-minutes" => config with { SustainDuration = TimeSpan.FromMinutes(double.Parse(value)) },
                "settle-minutes" => config with { SettleDuration = TimeSpan.FromMinutes(double.Parse(value)) },
                "subscribe-settle-seconds" => config with { SubscribeSettleDuration = TimeSpan.FromSeconds(double.Parse(value)) },
                "sample-seconds" => config with { SampleInterval = TimeSpan.FromSeconds(double.Parse(value)) },
                "health-interval-seconds" => config with { HealthReportInterval = TimeSpan.FromSeconds(double.Parse(value)) },
                "debug-probe-interval-seconds" => config with { DebugProbeInterval = TimeSpan.FromSeconds(double.Parse(value)) },
                "stream-probes-per-site" => config with { StreamProbesPerSite = int.Parse(value) },
                "sf-drain-messages" => config with { StoreAndForwardDrainMessages = int.Parse(value) },
                "slow-subscriber-events" => config with { SlowSubscriberEvents = int.Parse(value) },
                "data-dir" => config with { DataDirectory = value },
                "results" => config with { ResultsPath = value },
                _ => throw new ArgumentException($"Unknown option '--{key}'."),
            };
        }

        return config;
    }
}
