@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management
@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
@inject IBindingTester Tester

@if (_isVisible)
{
    <div class="modal show d-block" tabindex="-1" role="dialog" style="background-color: rgba(0,0,0,0.5);">
        <div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Test Bindings — @_instanceLabel</h5>
                    <button type="button" class="btn-close" @onclick="Close"></button>
                </div>
                <div class="modal-body">
                    <div class="d-flex align-items-center gap-2 mb-3">
                        <button class="btn btn-outline-primary btn-sm" @onclick="RefreshAsync" disabled="@_loading">
                            @(_loading ? "Loading…" : "Refresh")
                        </button>
                        <button class="btn btn-secondary btn-sm" @onclick="Close">Close</button>
                        @if (!_loading && _rows.Count > 0)
                        {
                            <span class="text-muted small ms-auto">Last read: @_lastReadAt.ToLocalTime().ToString("HH:mm:ss")</span>
                        }
                    </div>

                    @foreach (var banner in _connectionBanners)
                    {
                        <div class="alert alert-warning py-2 mb-2 small">
                            <strong>@banner.ConnectionName:</strong> @banner.Message
                        </div>
                    }

                    <div class="table-responsive">
                        <table class="table table-sm table-bordered mb-0">
                            <thead class="table-light">
                                <tr>
                                    <th>Attribute</th>
                                    <th>Connection</th>
                                    <th>Tag Path</th>
                                    <th>Value</th>
                                    <th style="width: 90px;">Quality</th>
                                    <th style="width: 110px;">Time</th>
                                </tr>
                            </thead>
                            <tbody>
                                @if (_rows.Count == 0)
                                {
                                    <tr>
                                        <td colspan="6" class="text-muted text-center small p-3">
                                            @(_loading ? "Loading…" : "No bound attributes to test.")
                                        </td>
                                    </tr>
                                }
                                else
                                {
                                    @foreach (var row in _rows)
                                    {
                                        var outcome = LookupOutcome(row);
                                        <tr>
                                            <td class="small">@row.AttributeName</td>
                                            <td class="small text-muted">@row.ConnectionName</td>
                                            <td class="small font-monospace text-break">@row.EffectiveTagPath</td>
                                            <td class="small font-monospace">
                                                @if (_loading)
                                                {
                                                    <em class="text-muted">…</em>
                                                }
                                                else if (outcome is null)
                                                {
                                                    <em class="text-muted">—</em>
                                                }
                                                else if (outcome.Success)
                                                {
                                                    @FormatValue(outcome.Value)
                                                }
                                                else
                                                {
                                                    <span class="text-muted">—</span>
                                                    <div class="text-danger small">error: @outcome.ErrorMessage</div>
                                                }
                                            </td>
                                            <td class="small">
                                                @if (_loading || outcome is null)
                                                {
                                                    <em class="text-muted">—</em>
                                                }
                                                else
                                                {
                                                    <span class="badge @QualityBadge(outcome.Quality)">@outcome.Quality</span>
                                                }
                                            </td>
                                            <td class="small text-muted">
                                                @if (_loading || outcome is null)
                                                {
                                                    @("—")
                                                }
                                                else
                                                {
                                                    @outcome.Timestamp.ToLocalTime().ToString("HH:mm:ss")
                                                }
                                            </td>
                                        </tr>
                                    }
                                }
                            </tbody>
                        </table>
                    </div>
                </div>
                <div class="modal-footer">
                    <button class="btn btn-secondary" @onclick="Close">Close</button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    /// <summary>
    /// A single binding row to test — built by the page at click time from its
    /// <c>_bindings</c> table and passed in via <see cref="ShowAsync"/>. Carries
    /// the effective tag path (override ?? template default) so the dialog
    /// doesn't need page-side knowledge to compute it.
    /// </summary>
    public sealed record BindingRowToTest(
        string AttributeName,
        string ConnectionName,
        string EffectiveTagPath);

    [Parameter] public EventCallback OnCancelled { get; set; }

    private sealed record ConnectionBanner(string ConnectionName, string Message);

    private bool _isVisible;
    private bool _loading;
    private string _instanceLabel = "";
    private string _runtimeSiteId = "";
    private DateTimeOffset _lastReadAt;
    private List<BindingRowToTest> _rows = new();
    private readonly List<ConnectionBanner> _connectionBanners = new();
    // Keyed by (connection, tagPath) so two connections referencing the same
    // tag path don't collide.
    private readonly Dictionary<(string Connection, string TagPath), TagReadOutcome> _outcomes = new();

    /// <summary>
    /// Opens the dialog and triggers an immediate one-shot read. Method-arg
    /// pattern (mirroring <c>NodeBrowserDialog.ShowAsync</c>) — Razor
    /// parameter binding would propagate on the next render and race the
    /// LoadAsync below.
    /// </summary>
    /// <param name="siteId">Site identifier (machine name) used by <see cref="IBindingTester"/> for routing.</param>
    /// <param name="rows">Rows to test (one per attribute with a connection + effective tag path).</param>
    /// <param name="instanceLabel">Optional label rendered in the modal header (instance unique name).</param>
    public async Task ShowAsync(
        string siteId,
        IReadOnlyList<BindingRowToTest> rows,
        string instanceLabel = "")
    {
        _runtimeSiteId = siteId;
        _instanceLabel = instanceLabel;
        _rows = rows.ToList();
        _outcomes.Clear();
        _connectionBanners.Clear();
        _isVisible = true;
        await LoadAsync();
    }

    private async Task LoadAsync()
    {
        if (_rows.Count == 0)
        {
            _loading = false;
            StateHasChanged();
            return;
        }

        _loading = true;
        _outcomes.Clear();
        _connectionBanners.Clear();
        StateHasChanged();

        // Group by connection name — one ReadTagValuesCommand per connection,
        // issued in parallel. Distinct tag paths only (a single attribute may
        // appear multiple times in the page, but reads are per (conn, tag)).
        var groups = _rows
            .GroupBy(r => r.ConnectionName, StringComparer.Ordinal)
            .Select(g => new
            {
                Connection = g.Key,
                TagPaths = g.Select(r => r.EffectiveTagPath).Distinct(StringComparer.Ordinal).ToList(),
            })
            .ToList();

        var tasks = groups.Select(g => Tester.ReadAsync(_runtimeSiteId, g.Connection, g.TagPaths)).ToArray();
        ReadTagValuesResult[] results;
        try
        {
            results = await Task.WhenAll(tasks);
        }
        catch (Exception ex)
        {
            // Last-ditch: a service-layer exception that escaped the typed
            // failure mapping (shouldn't happen — BindingTester translates
            // everything but OperationCanceledException). Surface as a single
            // banner so the dialog stays usable.
            _connectionBanners.Add(new ConnectionBanner("(all)", $"Read failed: {ex.Message}"));
            _loading = false;
            _lastReadAt = DateTimeOffset.UtcNow;
            StateHasChanged();
            return;
        }

        for (var i = 0; i < groups.Count; i++)
        {
            var group = groups[i];
            var result = results[i];

            if (result.Failure is not null)
            {
                _connectionBanners.Add(new ConnectionBanner(group.Connection, FormatFailure(result.Failure)));
                continue;
            }

            foreach (var outcome in result.Outcomes)
            {
                _outcomes[(group.Connection, outcome.TagPath)] = outcome;
            }
        }

        _lastReadAt = DateTimeOffset.UtcNow;
        _loading = false;
        StateHasChanged();
    }

    private Task RefreshAsync() => LoadAsync();

    private TagReadOutcome? LookupOutcome(BindingRowToTest row)
        => _outcomes.GetValueOrDefault((row.ConnectionName, row.EffectiveTagPath));

    // Maps ReadTagValuesFailureKind to a friendly banner. Raw failure.Message
    // is surfaced verbatim only for ServerError (which carries the adapter's
    // own message text).
    private static string FormatFailure(ReadTagValuesFailure failure) => failure.Kind switch
    {
        ReadTagValuesFailureKind.ConnectionNotFound      => "Connection no longer exists at the site.",
        ReadTagValuesFailureKind.ConnectionNotConnected  => "Connection not yet established — retry shortly.",
        ReadTagValuesFailureKind.Timeout                 => "Read timed out — the server may be slow. Try Refresh.",
        ReadTagValuesFailureKind.ServerError             => $"Server error: {failure.Message}",
        _                                                 => failure.Message,
    };

    private static string FormatValue(object? value) => value switch
    {
        null => "(null)",
        string s => s,
        IFormattable f => f.ToString(null, System.Globalization.CultureInfo.InvariantCulture),
        _ => value.ToString() ?? "(null)",
    };

    private static string QualityBadge(string quality) => quality switch
    {
        "Good" => "bg-success",
        "Uncertain" => "bg-warning text-dark",
        _ => "bg-danger",
    };

    private async Task Close()
    {
        _isVisible = false;
        await OnCancelled.InvokeAsync();
    }
}
