using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;

/// <summary>
/// Idempotent central startup normalizer that rewrites already-persisted List attribute
/// values from the old array-of-strings JSON form (<c>["10","20"]</c>) to the new
/// native-typed form (<c>[10,20]</c>).
///
/// <para>
/// This is a safety net: at the time of writing there is no deployed List attribute data,
/// so in practice it finds nothing to rewrite. It runs on every central startup after
/// migrations apply and MUST never abort startup for data reasons — every row's work is
/// wrapped in a per-row try/catch that logs and skips on malformed data. A second run
/// finds nothing to change (native → native re-encode is byte-identical).
/// </para>
/// </summary>
public static class ListValueNormalizer
{
    /// <summary>
    /// Rewrites old-form List attribute values to the native-typed JSON form across
    /// <see cref="ScadaBridgeDbContext.TemplateAttributes"/> and
    /// <see cref="ScadaBridgeDbContext.InstanceAttributeOverrides"/>. Idempotent and
    /// best-effort: malformed rows are logged and skipped, never rethrown.
    /// </summary>
    /// <param name="db">The configuration database context.</param>
    /// <param name="logger">Optional logger for diagnostics.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public static async Task NormalizeAsync(
        ScadaBridgeDbContext db,
        ILogger? logger = null,
        CancellationToken ct = default)
    {
        var rewritten = 0;

        // TemplateAttributes: List rows carry the element type on the row itself.
        var templateRows = await db.TemplateAttributes
            .Where(a => a.DataType == DataType.List)
            .ToListAsync(ct);

        foreach (var a in templateRows)
        {
            try
            {
                var native = AttributeValueCodec.Encode(
                    AttributeValueCodec.Decode(a.Value, DataType.List, a.ElementDataType));
                if (native != a.Value)
                {
                    a.Value = native;
                    rewritten++;
                }
            }
            catch (Exception ex)
            {
                // Never abort startup for a single bad row. FormatException from Decode is the
                // expected case; the broad catch also covers an unexpected serialize failure
                // (e.g. a JsonException on a non-finite value) so one poison row can't crash boot.
                logger?.LogWarning(ex,
                    "List value normalizer: skipping unprocessable list value for TemplateAttribute {Id}.",
                    a.Id);
            }
        }

        // InstanceAttributeOverrides: only rows that carry an element type are List rows.
        // Rows with a null ElementDataType are scalar/legacy rows (no deployed List data
        // exists, so none in practice) and are skipped.
        var overrideRows = await db.InstanceAttributeOverrides
            .Where(o => o.ElementDataType != null)
            .ToListAsync(ct);

        foreach (var o in overrideRows)
        {
            try
            {
                var native = AttributeValueCodec.Encode(
                    AttributeValueCodec.Decode(o.OverrideValue, DataType.List, o.ElementDataType));
                if (native != o.OverrideValue)
                {
                    o.OverrideValue = native;
                    rewritten++;
                }
            }
            catch (Exception ex)
            {
                logger?.LogWarning(ex,
                    "List value normalizer: skipping unprocessable list value for InstanceAttributeOverride {Id}.",
                    o.Id);
            }
        }

        try
        {
            await db.SaveChangesAsync(ct);
        }
        catch (Exception ex)
        {
            // A catastrophic DB failure on SaveChanges may propagate, but log it first so
            // startup diagnostics are not silent. Per-row data problems are already handled
            // above and never reach here.
            logger?.LogError(ex, "List value normalizer: SaveChanges failed.");
            throw;
        }

        if (rewritten > 0)
        {
            logger?.LogInformation(
                "List value normalizer: rewrote {n} attribute value(s) to native JSON.", rewritten);
        }
        else
        {
            logger?.LogDebug("List value normalizer: no attribute values required rewriting.");
        }
    }
}
