using System.Text.Json;

namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;

/// <summary>
/// Human-readable table formatter for <c>audit query --format table</c>.
/// Renders each fetched page as a column-aligned text table with a fixed
/// column set (<see cref="Columns"/>). Long free-text fields (Target, Actor) are
/// truncated with an ellipsis so columns stay aligned regardless of payload size.
/// </summary>
/// <remarks>
/// A header row is emitted once per page (matching the streamable, page-at-a-time
/// contract of <see cref="IAuditFormatter"/>). An empty page emits the header only,
/// so the column shape is visible even with zero results.
/// </remarks>
public sealed class TableAuditFormatter : IAuditFormatter
{
    /// <summary>JSON property name (camelCase, as the server serializes it) → column header.</summary>
    private static readonly (string Property, string Header, int MaxWidth)[] Columns =
    {
        ("occurredAtUtc", "OccurredAtUtc", 24),
        ("channel",       "Channel",       14),
        ("kind",          "Kind",          18),
        ("status",        "Status",        12),
        ("target",        "Target",        32),
        ("actor",         "Actor",         20),
        ("durationMs",    "DurationMs",    10),
        ("httpStatus",    "HttpStatus",    10),
    };

    /// <inheritdoc />
    public void WritePage(IReadOnlyList<JsonElement> events, TextWriter output)
    {
        // Build every cell first so column widths account for the actual data.
        var rows = new List<string[]>(events.Count);
        foreach (var evt in events)
        {
            var cells = new string[Columns.Length];
            for (var i = 0; i < Columns.Length; i++)
                cells[i] = Truncate(CellValue(evt, Columns[i].Property), Columns[i].MaxWidth);
            rows.Add(cells);
        }

        var widths = new int[Columns.Length];
        for (var i = 0; i < Columns.Length; i++)
            widths[i] = Columns[i].Header.Length;
        foreach (var row in rows)
            for (var i = 0; i < Columns.Length; i++)
                widths[i] = Math.Max(widths[i], row[i].Length);

        WriteRow(output, Columns.Select(c => c.Header).ToArray(), widths);
        foreach (var row in rows)
            WriteRow(output, row, widths);
    }

    /// <summary>
    /// Extracts a cell value for <paramref name="property"/> from an audit event.
    /// A missing property or a JSON <c>null</c> renders as an empty string (never
    /// the literal text "null").
    /// </summary>
    private static string CellValue(JsonElement evt, string property)
    {
        if (evt.ValueKind != JsonValueKind.Object
            || !evt.TryGetProperty(property, out var value)
            || value.ValueKind == JsonValueKind.Null)
        {
            return string.Empty;
        }

        return value.ValueKind == JsonValueKind.String
            ? value.GetString() ?? string.Empty
            : value.ToString();
    }

    /// <summary>
    /// Truncates <paramref name="value"/> to <paramref name="maxWidth"/> characters,
    /// replacing the tail with a single-character ellipsis so the column stays aligned.
    /// </summary>
    private static string Truncate(string value, int maxWidth)
    {
        if (maxWidth <= 0 || value.Length <= maxWidth)
            return value;
        if (maxWidth == 1)
            return "…";
        return value.Substring(0, maxWidth - 1) + "…";
    }

    private static void WriteRow(TextWriter output, IReadOnlyList<string> cells, int[] widths)
    {
        for (var i = 0; i < cells.Count; i++)
        {
            // Last column is not padded — avoids trailing whitespace at line end.
            output.Write(i == cells.Count - 1 ? cells[i] : cells[i].PadRight(widths[i] + 2));
        }
        output.WriteLine();
    }
}
