using Mbproxy.Options;
using Mbproxy.Proxy;
using Mbproxy.Proxy.Multiplexing;
using Mbproxy.Proxy.Supervision;
using Microsoft.Extensions.Options;

namespace Mbproxy.Admin;

/// <summary>
/// Pure orchestration: reads live state from injected singletons and builds a
/// <see cref="StatusResponse"/> for <c>GET /</c> and <c>GET /status.json</c>.
///
/// <para>No I/O; no side effects. Constructed once via DI; <see cref="Build"/> is the
/// only operation and may be called on any thread at any time.</para>
/// </summary>
internal sealed class StatusSnapshotBuilder
{
    private readonly IOptionsMonitor<MbproxyOptions> _options;
    private readonly ServiceCounters _serviceCounters;
    private readonly AssemblyVersionAccessor _version;
    private readonly ProxyWorker _proxyWorker;
    private readonly TagCaptureRegistry _captureRegistry;

    public StatusSnapshotBuilder(
        IOptionsMonitor<MbproxyOptions> options,
        ServiceCounters serviceCounters,
        AssemblyVersionAccessor version,
        ProxyWorker proxyWorker,
        TagCaptureRegistry captureRegistry)
    {
        _options         = options;
        _serviceCounters = serviceCounters;
        _version         = version;
        _proxyWorker     = proxyWorker;
        _captureRegistry = captureRegistry;
    }

    /// <summary>
    /// Builds the connection-detail debug snapshot for one PLC: the last value observed
    /// for every configured BCD tag. Returns an empty, disarmed snapshot when
    /// <paramref name="plcName"/> is unknown (e.g. a detail page open for a PLC removed
    /// by hot-reload).
    /// </summary>
    public PlcDebugSnapshot BuildDebug(string plcName)
    {
        if (!_captureRegistry.TryGet(plcName, out var capture))
            return new PlcDebugSnapshot(CaptureArmed: false, Tags: Array.Empty<TagValueDto>());

        var now  = DateTimeOffset.UtcNow;
        var tags = capture.Snapshot()
            .Select(o => ToTagDto(o, now))
            .ToList();

        return new PlcDebugSnapshot(capture.IsArmed, tags);
    }

    private static TagValueDto ToTagDto(TagValueObservation o, DateTimeOffset now)
    {
        bool hasValue = o.UpdatedAtUtc.HasValue;

        string rawHex = !hasValue
            ? "—"
            : o.Width == 32
                ? $"0x{o.RawHigh:X4}{o.RawLow:X4}"
                : $"0x{o.RawLow:X4}";

        return new TagValueDto(
            Address:      o.Address,
            Width:        o.Width,
            HasValue:     hasValue,
            Direction:    o.Direction == CaptureDirection.Write ? "write" : "read",
            RawHex:       rawHex,
            DecodedValue: o.DecodedValue,
            UpdatedAtUtc: o.UpdatedAtUtc?.ToString("o"),
            AgeSeconds:   o.UpdatedAtUtc is { } at ? (now - at).TotalSeconds : null);
    }

    /// <summary>
    /// Builds a point-in-time <see cref="StatusResponse"/>.
    /// Each counter is read atomically; no locks are held across the build.
    /// </summary>
    public StatusResponse Build()
    {
        var opts      = _options.CurrentValue;
        var now       = DateTimeOffset.UtcNow;
        var started   = _serviceCounters.StartedAtUtc;
        var uptime    = (long)(now - started).TotalSeconds;
        var supervisors = _proxyWorker.Supervisors;

        // ── Build per-PLC status rows ─────────────────────────────────────────
        var plcStatuses = new List<PlcStatus>(opts.Plcs.Count);
        int boundCount  = 0;

        foreach (var plc in opts.Plcs)
        {
            supervisors.TryGetValue(plc.Name, out var supervisor);

            // Supervisor state
            SupervisorSnapshot? snap = supervisor?.Snapshot();
            string stateStr = snap?.State switch
            {
                SupervisorState.Bound      => "bound",
                SupervisorState.Recovering => "recovering",
                _                          => "stopped",
            };
            if (snap?.State == SupervisorState.Bound) boundCount++;

            // Per-client snapshots
            var activeUpstreams = supervisor?.ActiveUpstreams ?? Array.Empty<UpstreamPipe>();
            var clientSnapshots = activeUpstreams
                .Select(p => new ClientSnapshot(
                    Remote:         p.RemoteEp?.ToString() ?? "?",
                    ConnectedAtUtc: p.ConnectedAtUtc,
                    PdusForwarded:  p.PdusForwardedCount))
                .ToList();

            // Counter snapshot
            var counters = supervisor?.CurrentCounters.Snapshot()
                        ?? new CounterSnapshot(
                            PdusForwarded:             0,
                            Fc03:                      0,
                            Fc04:                      0,
                            Fc06:                      0,
                            Fc16:                      0,
                            FcOther:                   0,
                            RewrittenSlots:            0,
                            PartialBcdWarnings:        0,
                            InvalidBcdWarnings:        0,
                            BackendException01:        0,
                            BackendException02:        0,
                            BackendException03:        0,
                            BackendException04:        0,
                            BackendExceptionOther:     0,
                            BytesUpstreamIn:           0,
                            BytesUpstreamOut:          0,
                            RecoveryAttempts:          0,
                            LastBindError:             null,
                            LastRoundTripMs:           0.0,
                            ConnectsSuccess:           0,
                            ConnectsFailed:            0,
                            InFlightCount:             0,
                            MaxInFlight:               0,
                            TxIdWraps:                 0,
                            BackendDisconnectCascades: 0,
                            BackendQueueDepth:         0,
                            CoalescedHitCount:         0,
                            CoalescedMissCount:        0,
                            CoalescedResponseToDeadUpstream: 0,
                            CacheHitCount:             0,
                            CacheMissCount:            0,
                            CacheInvalidations:        0,
                            CacheEntryCount:           0,
                            CacheBytes:                0,
                            ResponseDropForFullUpstream: 0,
                            BackendHeartbeatsSent:     0,
                            BackendHeartbeatsFailed:   0,
                            BackendIdleDisconnects:    0);

            long connectsSuccess = counters.ConnectsSuccess;
            long connectsFailed  = counters.ConnectsFailed;

            plcStatuses.Add(new PlcStatus(
                Name:       plc.Name,
                Host:       plc.Host,
                ListenPort: plc.ListenPort,
                Listener: new PlcListenerStatus(
                    State:            stateStr,
                    LastBindError:    snap?.LastBindError,
                    RecoveryAttempts: snap?.RecoveryAttempts ?? 0),
                Clients: new PlcClientsStatus(
                    Connected:       clientSnapshots.Count,
                    RemoteEndpoints: clientSnapshots),
                Pdus: new PlcPdusStatus(
                    Forwarded:          counters.PdusForwarded,
                    ByFc:               new FcCounts(counters.Fc03, counters.Fc04, counters.Fc06, counters.Fc16, counters.FcOther),
                    RewrittenSlots:     counters.RewrittenSlots,
                    PartialBcdWarnings: counters.PartialBcdWarnings,
                    InvalidBcdWarnings: counters.InvalidBcdWarnings),
                Backend: new PlcBackendStatus(
                    ConnectsSuccess:    connectsSuccess,
                    ConnectsFailed:     connectsFailed,
                    ExceptionsByCode:   new ExceptionCounts(
                        counters.BackendException01,
                        counters.BackendException02,
                        counters.BackendException03,
                        counters.BackendException04,
                        counters.BackendExceptionOther),
                    LastRoundTripMs:    counters.LastRoundTripMs,
                    InFlight:           counters.InFlightCount,
                    MaxInFlight:        counters.MaxInFlight,
                    TxIdWraps:          counters.TxIdWraps,
                    DisconnectCascades: counters.BackendDisconnectCascades,
                    QueueDepth:         counters.BackendQueueDepth,
                    CoalescedHitCount:  counters.CoalescedHitCount,
                    CoalescedMissCount: counters.CoalescedMissCount,
                    CoalescedResponseToDeadUpstream: counters.CoalescedResponseToDeadUpstream,
                    CacheHitCount:      counters.CacheHitCount,
                    CacheMissCount:     counters.CacheMissCount,
                    CacheInvalidations: counters.CacheInvalidations,
                    CacheEntryCount:    counters.CacheEntryCount,
                    CacheBytes:         counters.CacheBytes,
                    BackendHeartbeatsSent:   counters.BackendHeartbeatsSent,
                    BackendHeartbeatsFailed: counters.BackendHeartbeatsFailed,
                    BackendIdleDisconnects:  counters.BackendIdleDisconnects),
                Bytes: new PlcBytesStatus(
                    UpstreamIn:  counters.BytesUpstreamIn,
                    UpstreamOut: counters.BytesUpstreamOut)));
        }

        // ── Service-wide fields ───────────────────────────────────────────────
        var service = new ServiceFields(
            UptimeSeconds:           uptime,
            Version:                 _version.Version,
            ConfigLastReloadUtc:     _serviceCounters.LastReloadUtc,
            ConfigReloadCount:       _serviceCounters.ReloadAppliedCount,
            ConfigReloadRejectedCount: _serviceCounters.ReloadRejectedCount);

        var listeners = new ListenersAggregate(
            Bound:      boundCount,
            Configured: opts.Plcs.Count);

        return new StatusResponse(service, listeners, plcStatuses);
    }
}
