@namespace ZB.MOM.WW.Secrets.Ui.Components
@implements IDisposable
@inject ISecretStore Store
@inject ISecretCipher Cipher
@inject IAuditWriter Audit
@inject IServiceProvider Services
@* Components/RevealButton.razor — a policy-gated, audited reveal. The plaintext is loaded and
   shown only AFTER the click, straight from the store+cipher (a human reveal shows the CURRENT
   value, distinct from any cached machine resolve). The value never enters an audit field. *@

@if (!_revealed)
{
    <button type="button" class="btn btn-outline-secondary reveal-secret"
            disabled="@_busy" data-secret-name="@Name.Value"
            data-testid="reveal-button" @onclick="RevealAsync">
        Reveal
    </button>
}
else
{
    <div class="reveal-panel" data-testid="reveal-panel">
        @if (_value is not null)
        {
            <code class="revealed-value" data-testid="revealed-value">@_value</code>
        }
        else
        {
            <span class="field-error s-bad" data-testid="reveal-error">@(_error ?? "Unavailable.")</span>
        }
        <button type="button" class="btn btn-link hide-secret"
                data-testid="hide-button" @onclick="Hide">
            Hide
        </button>
    </div>
}

@code {
    private readonly CancellationTokenSource _cts = new();
    private bool _revealed;
    private string? _value;
    private string? _error;
    private bool _busy;

    /// <summary>The secret whose plaintext this button reveals.</summary>
    [Parameter] public SecretName Name { get; set; }

    /// <summary>The cascading authentication state, used as the fallback source of the audit actor.</summary>
    [CascadingParameter] private Task<AuthenticationState>? AuthState { get; set; }

    private async Task RevealAsync()
    {
        _busy = true;
        AuditOutcome outcome;
        try
        {
            StoredSecret? row = await Store.GetAsync(Name, _cts.Token);
            if (row is null)
            {
                _value = null;
                _error = "Secret not found.";
                outcome = AuditOutcome.Failure;
            }
            else
            {
                _value = Cipher.Decrypt(row);
                _error = null;
                outcome = AuditOutcome.Success;
            }
        }
        catch (SecretDecryptionException)
        {
            _value = null;
            _error = "Unable to decrypt secret.";
            outcome = AuditOutcome.Failure;
        }
        catch (Exception)
        {
            // Any other unexpected store/cipher fault (e.g. master key unavailable, timeout) must
            // still be audited as a Failure and surfaced to the user, not left to tear down the
            // circuit. The value is never shown.
            _value = null;
            _error = "Unable to reveal secret.";
            outcome = AuditOutcome.Failure;
        }
        finally
        {
            _busy = false;
            _revealed = true;
        }

        string actor = await SecretActorResolver.ResolveAsync(Services, AuthState);
        try
        {
            await Audit.WriteAsync(
                new AuditEvent
                {
                    EventId = Guid.NewGuid(),
                    OccurredAtUtc = DateTimeOffset.UtcNow,
                    Actor = actor,
                    Action = "secret.reveal",
                    Outcome = outcome,
                    Category = "Secrets",
                    Target = Name.Value,
                    DetailsJson = outcome == AuditOutcome.Failure ? "{\"reason\":\"error\"}" : null,
                },
                _cts.Token);
        }
        catch
        {
            // Best-effort audit; never let an audit-write fault surface into the event pipeline.
        }
    }

    private void Hide()
    {
        _revealed = false;
        _value = null;
        _error = null;
    }

    /// <inheritdoc />
    public void Dispose()
    {
        _cts.Cancel();
        _cts.Dispose();
    }
}
