@page "/certificates"
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = AdminRoles.FleetAdmin)]
@using ZB.MOM.WW.OtOpcUa.Admin.Services
@inject CertTrustService Certs
@inject AuthenticationStateProvider AuthState
@inject ILogger<Certificates> Log

<h1 class="mb-4">Certificate trust</h1>

<div class="alert alert-info small mb-4">
    PKI store root <code>@Certs.PkiStoreRoot</code>. Trusting a rejected cert moves the file into the trusted store — the OPC UA server picks up the change on the next client handshake, so operators should retry the rejected client's connection after trusting.
</div>

@if (_status is not null)
{
    <div class="alert alert-@_statusKind alert-dismissible">
        @_status
        <button type="button" class="btn-close" @onclick="ClearStatus"></button>
    </div>
}

<h2 class="h4">Rejected (@_rejected.Count)</h2>
@if (_rejected.Count == 0)
{
    <p class="text-muted">No rejected certificates. Clients that fail to handshake with an untrusted cert land here.</p>
}
else
{
    <table class="table table-sm align-middle">
        <thead><tr><th>Subject</th><th>Issuer</th><th>Thumbprint</th><th>Valid</th><th class="text-end">Actions</th></tr></thead>
        <tbody>
        @foreach (var c in _rejected)
        {
            <tr>
                <td>@c.Subject</td>
                <td>@c.Issuer</td>
                <td><code class="small">@c.Thumbprint</code></td>
                <td class="small">@c.NotBefore.ToString("yyyy-MM-dd") → @c.NotAfter.ToString("yyyy-MM-dd")</td>
                <td class="text-end">
                    <button class="btn btn-sm btn-success me-1" @onclick="() => TrustAsync(c)">Trust</button>
                    <button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteRejectedAsync(c)">Delete</button>
                </td>
            </tr>
        }
        </tbody>
    </table>
}

<h2 class="h4 mt-5">Trusted (@_trusted.Count)</h2>
@if (_trusted.Count == 0)
{
    <p class="text-muted">No client certs have been explicitly trusted. The server's own application cert lives in <code>own/</code> and is not listed here.</p>
}
else
{
    <table class="table table-sm align-middle">
        <thead><tr><th>Subject</th><th>Issuer</th><th>Thumbprint</th><th>Valid</th><th class="text-end">Actions</th></tr></thead>
        <tbody>
        @foreach (var c in _trusted)
        {
            <tr>
                <td>@c.Subject</td>
                <td>@c.Issuer</td>
                <td><code class="small">@c.Thumbprint</code></td>
                <td class="small">@c.NotBefore.ToString("yyyy-MM-dd") → @c.NotAfter.ToString("yyyy-MM-dd")</td>
                <td class="text-end">
                    <button class="btn btn-sm btn-outline-danger" @onclick="() => UntrustAsync(c)">Revoke</button>
                </td>
            </tr>
        }
        </tbody>
    </table>
}

@code {
    private IReadOnlyList<CertInfo> _rejected = [];
    private IReadOnlyList<CertInfo> _trusted = [];
    private string? _status;
    private string _statusKind = "success";

    protected override void OnInitialized() => Reload();

    private void Reload()
    {
        _rejected = Certs.ListRejected();
        _trusted = Certs.ListTrusted();
    }

    private async Task TrustAsync(CertInfo c)
    {
        if (Certs.TrustRejected(c.Thumbprint))
        {
            await LogActionAsync("cert.trust", c);
            Set($"Trusted cert {c.Subject} ({Short(c.Thumbprint)}).", "success");
        }
        else
        {
            Set($"Could not trust {Short(c.Thumbprint)} — file missing; another admin may have already handled it.", "warning");
        }
        Reload();
    }

    private async Task DeleteRejectedAsync(CertInfo c)
    {
        if (Certs.DeleteRejected(c.Thumbprint))
        {
            await LogActionAsync("cert.delete.rejected", c);
            Set($"Deleted rejected cert {c.Subject} ({Short(c.Thumbprint)}).", "success");
        }
        else
        {
            Set($"Could not delete {Short(c.Thumbprint)} — file missing.", "warning");
        }
        Reload();
    }

    private async Task UntrustAsync(CertInfo c)
    {
        if (Certs.UntrustCert(c.Thumbprint))
        {
            await LogActionAsync("cert.untrust", c);
            Set($"Revoked trust for {c.Subject} ({Short(c.Thumbprint)}).", "success");
        }
        else
        {
            Set($"Could not revoke {Short(c.Thumbprint)} — file missing.", "warning");
        }
        Reload();
    }

    private async Task LogActionAsync(string action, CertInfo c)
    {
        // Cert trust changes are operator-initiated and security-sensitive — Serilog captures the
        // user + thumbprint trail. CertTrustService also logs at Information on each filesystem
        // move/delete; this line ties the action to the authenticated admin user so the two logs
        // correlate. DB-level ConfigAuditLog persistence is deferred — its schema is
        // cluster-scoped and cert actions are cluster-agnostic.
        var state = await AuthState.GetAuthenticationStateAsync();
        var user = state.User.Identity?.Name ?? "(anonymous)";
        Log.LogInformation("Admin cert action: user={User} action={Action} thumbprint={Thumbprint} subject={Subject}",
            user, action, c.Thumbprint, c.Subject);
    }

    private void Set(string message, string kind)
    {
        _status = message;
        _statusKind = kind;
    }

    private void ClearStatus() => _status = null;

    private static string Short(string thumbprint) =>
        thumbprint.Length > 12 ? thumbprint[..12] + "…" : thumbprint;
}
