using System.Globalization;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;

/// <summary>
///     Renders <see cref="DataValueSnapshot"/> + <see cref="WriteResult"/> payloads as the
///     plain-text lines every driver CLI prints to its console. Matches the one-field-per-line
///     style the existing OPC UA <c>otopcua-cli</c> uses so combined runs (read a tag via both
///     CLIs side-by-side) look coherent.
/// </summary>
public static class SnapshotFormatter
{
    /// <summary>
    ///     Single-tag multi-line render. Shape:
    ///     <code>
    ///     Tag:         &lt;name&gt;
    ///     Value:       &lt;value&gt;
    ///     Status:      0x... (Good|BadCommunicationError|...)
    ///     Source Time: 2026-04-21T12:34:56.789Z
    ///     Server Time: 2026-04-21T12:34:56.790Z
    ///     </code>
    /// </summary>
    public static string Format(string tagName, DataValueSnapshot snapshot)
    {
        ArgumentNullException.ThrowIfNull(snapshot);
        var lines = new[]
        {
            $"Tag:         {tagName}",
            $"Value:       {FormatValue(snapshot.Value)}",
            $"Status:      {FormatStatus(snapshot.StatusCode)}",
            $"Source Time: {FormatTimestamp(snapshot.SourceTimestampUtc)}",
            $"Server Time: {FormatTimestamp(snapshot.ServerTimestampUtc)}",
        };
        return string.Join(Environment.NewLine, lines);
    }

    /// <summary>
    ///     Write-result render, one line: <c>Write &lt;tag&gt;: 0x... (Good|...)</c>.
    /// </summary>
    public static string FormatWrite(string tagName, WriteResult result)
    {
        ArgumentNullException.ThrowIfNull(result);
        return $"Write {tagName}: {FormatStatus(result.StatusCode)}";
    }

    /// <summary>
    ///     Table-style render for batch reads. Emits an aligned 4-column layout:
    ///     tag / value / status / source-time.
    /// </summary>
    public static string FormatTable(
        IReadOnlyList<string> tagNames, IReadOnlyList<DataValueSnapshot> snapshots)
    {
        ArgumentNullException.ThrowIfNull(tagNames);
        ArgumentNullException.ThrowIfNull(snapshots);
        if (tagNames.Count != snapshots.Count)
            throw new ArgumentException(
                $"tagNames ({tagNames.Count}) and snapshots ({snapshots.Count}) must be the same length");

        var rows = tagNames.Select((t, i) => new
        {
            Tag = t,
            Value = FormatValue(snapshots[i].Value),
            Status = FormatStatus(snapshots[i].StatusCode),
            Time = FormatTimestamp(snapshots[i].SourceTimestampUtc),
        }).ToArray();

        int tagW = Math.Max("TAG".Length, rows.Max(r => r.Tag.Length));
        int valW = Math.Max("VALUE".Length, rows.Max(r => r.Value.Length));
        int statW = Math.Max("STATUS".Length, rows.Max(r => r.Status.Length));
        // source-time column is fixed-width (ISO-8601 to ms) so no max-measurement needed.

        var sb = new System.Text.StringBuilder();
        sb.Append("TAG".PadRight(tagW)).Append("  ")
          .Append("VALUE".PadRight(valW)).Append("  ")
          .Append("STATUS".PadRight(statW)).Append("  ")
          .Append("SOURCE TIME").AppendLine();
        sb.Append(new string('-', tagW)).Append("  ")
          .Append(new string('-', valW)).Append("  ")
          .Append(new string('-', statW)).Append("  ")
          .Append(new string('-', "SOURCE TIME".Length)).AppendLine();
        foreach (var r in rows)
        {
            sb.Append(r.Tag.PadRight(tagW)).Append("  ")
              .Append(r.Value.PadRight(valW)).Append("  ")
              .Append(r.Status.PadRight(statW)).Append("  ")
              .Append(r.Time).AppendLine();
        }
        return sb.ToString().TrimEnd();
    }

    public static string FormatValue(object? value) => value switch
    {
        null => "<null>",
        bool b => b ? "true" : "false",
        string s => $"\"{s}\"",
        IFormattable f => f.ToString(null, CultureInfo.InvariantCulture),
        _ => value.ToString() ?? "<null>",
    };

    public static string FormatStatus(uint statusCode)
    {
        // Match the OPC UA shorthand for the statuses most-likely to land in a CLI run.
        // Anything outside this short-list surfaces as hex — operators can cross-reference
        // against OPC UA Part 6 § 7.34 (StatusCode tables) or Core.Abstractions status mappers.
        var name = statusCode switch
        {
            0x00000000u => "Good",
            0x80000000u => "Bad",
            0x80050000u => "BadCommunicationError",
            0x80060000u => "BadTimeout",
            0x80070000u => "BadNoCommunication",
            0x80080000u => "BadWaitingForInitialData",
            0x80340000u => "BadNodeIdUnknown",
            0x80350000u => "BadNodeIdInvalid",
            0x80740000u => "BadTypeMismatch",
            0x40000000u => "Uncertain",
            _ => null,
        };
        return name is null
            ? $"0x{statusCode:X8}"
            : $"0x{statusCode:X8} ({name})";
    }

    public static string FormatTimestamp(DateTime? ts)
    {
        if (ts is null) return "-";
        var utc = ts.Value.Kind == DateTimeKind.Utc ? ts.Value : ts.Value.ToUniversalTime();
        return utc.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
    }
}
