@page "/monitoring/audit-log"
@using ScadaLink.Security
@using ScadaLink.CentralUI.Components
@using ScadaLink.Commons.Entities.Audit
@using ScadaLink.Commons.Interfaces.Repositories
@attribute [Authorize(Policy = AuthorizationPolicies.RequireAdmin)]
@inject ICentralUiRepository CentralUiRepository
@inject IJSRuntime JS

<div class="container-fluid mt-3">
    <h4 class="mb-3">Audit Log</h4>

    <ToastNotification @ref="_toast" />

    <div class="row mb-3 g-2 align-items-end">
        <div class="col-md-2">
            <label class="form-label small" for="audit-filter-user">User</label>
            <input id="audit-filter-user"
                   type="text"
                   class="form-control form-control-sm"
                   aria-label="User"
                   @bind="_filterUser"
                   placeholder="Username" />
        </div>
        <div class="col-md-2">
            <label class="form-label small" for="audit-filter-entity-type">Entity Type</label>
            <input id="audit-filter-entity-type"
                   type="text"
                   class="form-control form-control-sm"
                   aria-label="Entity type"
                   @bind="_filterEntityType"
                   placeholder="e.g. Template" />
        </div>
        <div class="col-md-2">
            <label class="form-label small" for="audit-filter-action">Action</label>
            <input id="audit-filter-action"
                   type="text"
                   class="form-control form-control-sm"
                   aria-label="Action"
                   @bind="_filterAction"
                   placeholder="e.g. Create" />
        </div>
        <div class="col-md-2">
            <label class="form-label small" for="audit-filter-from">From</label>
            <input id="audit-filter-from"
                   type="datetime-local"
                   class="form-control form-control-sm"
                   aria-label="From timestamp"
                   @bind="_filterFrom" />
        </div>
        <div class="col-md-2">
            <label class="form-label small" for="audit-filter-to">To</label>
            <input id="audit-filter-to"
                   type="datetime-local"
                   class="form-control form-control-sm"
                   aria-label="To timestamp"
                   @bind="_filterTo" />
        </div>
        <div class="col-md-2 d-flex gap-1">
            <button class="btn btn-primary btn-sm" @onclick="Search" disabled="@_searching">
                @if (_searching) { <span class="spinner-border spinner-border-sm me-1" role="status"></span> }
                Search
            </button>
            <button class="btn btn-outline-secondary btn-sm" @onclick="ClearFilters" disabled="@_searching">
                Clear filters
            </button>
        </div>
    </div>

    @if (_errorMessage != null)
    {
        <div class="alert alert-danger">@_errorMessage</div>
    }

    @if (_entries != null)
    {
        <table class="table table-sm table-striped table-hover">
            <thead class="table-dark">
                <tr>
                    <th>Timestamp</th>
                    <th>User</th>
                    <th>Action</th>
                    <th>Entity Type</th>
                    <th>Entity ID</th>
                    <th>Entity Name</th>
                    <th>State</th>
                </tr>
            </thead>
            <tbody>
                @if (_entries.Count == 0)
                {
                    <tr><td colspan="7" class="text-muted text-center">No audit entries found.</td></tr>
                }
                @foreach (var entry in _entries)
                {
                    var entityIdShort = entry.EntityId is { Length: > 0 }
                        ? entry.EntityId[..Math.Min(12, entry.EntityId.Length)]
                        : "";
                    var hasState = !string.IsNullOrWhiteSpace(entry.AfterStateJson);
                    var isLarge = hasState && entry.AfterStateJson!.Length > 1024;
                    <tr>
                        <td class="small"><TimestampDisplay Value="@entry.Timestamp" /></td>
                        <td class="small">@entry.User</td>
                        <td><span class="badge @GetActionBadge(entry.Action)">@entry.Action</span></td>
                        <td class="small">@entry.EntityType</td>
                        <td class="small">
                            @if (!string.IsNullOrEmpty(entry.EntityId))
                            {
                                <code>@entityIdShort…</code>
                                <button class="btn btn-link btn-sm p-0 ms-1"
                                        @onclick="() => CopyAsync(entry.EntityId)"
                                        title="Copy entity ID"
                                        aria-label="Copy entity ID @entry.EntityId">📋</button>
                            }
                            else
                            {
                                <span class="text-muted">—</span>
                            }
                        </td>
                        <td class="small">@entry.EntityName</td>
                        <td>
                            @if (hasState)
                            {
                                if (isLarge)
                                {
                                    <button class="btn btn-outline-info btn-sm py-0 px-1"
                                            @onclick="() => ShowStateModal(entry)"
                                            aria-label="Open state details in modal for audit entry @entry.Id">
                                        View in modal
                                    </button>
                                }
                                else
                                {
                                    <button class="btn btn-outline-info btn-sm py-0 px-1"
                                            @onclick="() => ToggleStateView(entry.Id)"
                                            aria-label="Toggle state details for audit entry @entry.Id">
                                        @(_expandedEntryId == entry.Id ? "Hide" : "View")
                                    </button>
                                }
                            }
                            else
                            {
                                <span class="text-muted small">—</span>
                            }
                        </td>
                    </tr>
                    @if (hasState && !isLarge && _expandedEntryId == entry.Id)
                    {
                        <tr>
                            <td colspan="7">
                                <pre class="bg-light p-2 rounded small mb-0" style="max-height: 200px; overflow: auto;">@FormatJson(entry.AfterStateJson!)</pre>
                            </td>
                        </tr>
                    }
                }
            </tbody>
        </table>

        <div class="d-flex justify-content-between align-items-center">
            <span class="text-muted small">Page @_page of @TotalPages (@_totalCount total)</span>
            <div>
                <button class="btn btn-outline-secondary btn-sm me-1" @onclick="PrevPage" disabled="@(_page <= 1)">Previous</button>
                <button class="btn btn-outline-secondary btn-sm" @onclick="NextPage" disabled="@(!HasMore)">Next</button>
            </div>
        </div>
    }

    @if (_modalEntry != null)
    {
        <div class="modal-backdrop fade show"></div>
        <div class="modal fade show d-block" tabindex="-1" role="dialog">
            <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">
                            Audit entry @_modalEntry.Id — @_modalEntry.EntityType state
                        </h5>
                        <button type="button" class="btn-close" @onclick="CloseStateModal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <pre class="bg-light p-2 rounded small mb-0">@FormatJson(_modalEntry.AfterStateJson!)</pre>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="CloseStateModal">Close</button>
                    </div>
                </div>
            </div>
        </div>
    }
</div>

@code {
    private string? _filterUser;
    private string? _filterEntityType;
    private string? _filterAction;
    private DateTime? _filterFrom;
    private DateTime? _filterTo;

    // The datetime-local filter inputs are in the browser's local time zone.
    // This holds new Date().getTimezoneOffset() so the values are converted to
    // UTC (CentralUI-008) rather than relabelled. Until JS interop runs it is 0
    // (UTC), which is a safe default for a UTC server/browser.
    private int _browserUtcOffsetMinutes;

    private List<AuditLogEntry>? _entries;
    private int _totalCount;
    private int _page = 1;
    private int _pageSize = 50;
    private bool _searching;
    private string? _errorMessage;
    private int? _expandedEntryId;
    private AuditLogEntry? _modalEntry;

    private ToastNotification _toast = default!;

    private int TotalPages => _pageSize > 0 ? Math.Max(1, (_totalCount + _pageSize - 1) / _pageSize) : 1;
    private bool HasMore => _page * _pageSize < _totalCount;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender) return;
        try
        {
            // Date.getTimezoneOffset() returns (UTC - local) in minutes.
            _browserUtcOffsetMinutes = await JS.InvokeAsync<int>(
                "eval", "new Date().getTimezoneOffset()");
        }
        catch (Exception ex) when (ex is JSException or JSDisconnectedException
            or InvalidOperationException or TaskCanceledException)
        {
            // Prerender or a disconnected circuit: fall back to UTC (offset 0).
            _browserUtcOffsetMinutes = 0;
        }
    }

    private async Task Search()
    {
        _page = 1;
        await FetchPage();
    }

    private async Task ClearFilters()
    {
        _filterUser = null;
        _filterEntityType = null;
        _filterAction = null;
        _filterFrom = null;
        _filterTo = null;
        _page = 1;
        await FetchPage();
    }

    private async Task PrevPage() { _page--; await FetchPage(); }
    private async Task NextPage() { _page++; await FetchPage(); }

    private async Task FetchPage()
    {
        _searching = true;
        _errorMessage = null;
        try
        {
            var (entries, totalCount) = await CentralUiRepository.GetAuditLogEntriesAsync(
                user: string.IsNullOrWhiteSpace(_filterUser) ? null : _filterUser.Trim(),
                entityType: string.IsNullOrWhiteSpace(_filterEntityType) ? null : _filterEntityType.Trim(),
                action: string.IsNullOrWhiteSpace(_filterAction) ? null : _filterAction.Trim(),
                from: BrowserTime.LocalInputToUtc(_filterFrom, _browserUtcOffsetMinutes),
                to: BrowserTime.LocalInputToUtc(_filterTo, _browserUtcOffsetMinutes),
                page: _page,
                pageSize: _pageSize);

            _entries = entries.ToList();
            _totalCount = totalCount;
        }
        catch (Exception ex)
        {
            _errorMessage = $"Query failed: {ex.Message}";
        }
        _searching = false;
    }

    private void ToggleStateView(int entryId)
    {
        _expandedEntryId = _expandedEntryId == entryId ? null : entryId;
    }

    private void ShowStateModal(AuditLogEntry entry)
    {
        _modalEntry = entry;
    }

    private void CloseStateModal()
    {
        _modalEntry = null;
    }

    private async Task CopyAsync(string text)
    {
        try
        {
            await JS.InvokeVoidAsync("navigator.clipboard.writeText", text);
            _toast.ShowSuccess("Copied to clipboard.");
        }
        catch
        {
            _toast.ShowError("Copy failed.");
        }
    }

    private static string GetActionBadge(string action) => action switch
    {
        "Create" => "bg-success",
        "Update" => "bg-primary",
        "Delete" => "bg-danger",
        "Deploy" => "bg-info text-dark",
        _ => "bg-secondary"
    };

    private static string FormatJson(string json)
    {
        try
        {
            var doc = System.Text.Json.JsonDocument.Parse(json);
            return System.Text.Json.JsonSerializer.Serialize(doc, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
        }
        catch
        {
            return json;
        }
    }
}
