using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Deployment;

/// <summary>
/// MV-14: the Instance Configure attribute-override panel uses the shared
/// <c>AttributeListEditor</c> for a List attribute (whole-list replacement; the
/// element type is fixed by the base attribute, so the type select is hidden via
/// <c>ShowElementType="false"</c>). Loading an existing override decodes its JSON
/// into rows; saving encodes the rows back to canonical JSON with a pre-submit
/// round-trip guard; clearing removes the override row. <c>InstanceConfigure</c>
/// is a heavyweight page (multiple injected services incl. <c>InstanceService</c>
/// and the flattening pipeline), so — consistent with the native-alarm and
/// template-editor coverage — these are structural assertions over the component
/// source that pin the wiring, plus a real codec round-trip mirroring what the
/// page does on load/save.
/// </summary>
public class InstanceConfigureListOverrideTests
{
    private static string InstanceConfigureMarkup
    {
        get
        {
            var dir = AppContext.BaseDirectory;
            for (var i = 0; i < 6 && dir is not null; i++)
                dir = Directory.GetParent(dir)?.FullName;
            return File.ReadAllText(Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI",
                "Components", "Pages", "Deployment", "InstanceConfigure.razor"));
        }
    }

    [Fact]
    public void ListOverride_RevealsSharedEditor_WithElementTypeHidden()
    {
        var markup = InstanceConfigureMarkup;
        // Conditional reveal on a List attribute.
        Assert.Contains("attr.DataType == DataType.List", markup);
        Assert.Contains("<AttributeListEditor", markup);
        // Element type is fixed by the base attribute → type select hidden.
        Assert.Contains("ShowElementType=\"false\"", markup);
        Assert.Contains("ElementDataType=\"@(attr.ElementDataType ?? DataType.String)\"", markup);
        // Bound to the per-attribute working rows.
        Assert.Contains("Rows=\"@GetListRows(attr.Name)\"", markup);
        Assert.Contains("RowsChanged=\"@(r => OnListRowsChanged(attr.Name, r))\"", markup);
    }

    [Fact]
    public void ListOverride_DecodesOnLoad_AndEncodesOnSaveWithGuard()
    {
        var markup = InstanceConfigureMarkup;
        // Load: effective value (existing override JSON or template default)
        // decoded into rows via the shared codec, malformed → empty rows.
        Assert.Contains("DecodeListRows(", markup);
        Assert.Contains("catch (FormatException)", markup);
        // Save: rows encoded to canonical JSON + round-trip Decode guard.
        Assert.Contains("AttributeValueCodec.Encode(GetListRows(", markup);
        Assert.Contains("AttributeValueCodec.Decode(json, DataType.List, elementType)", markup);
        Assert.Contains("_overrideErrors", markup);
    }

    [Fact]
    public void ListOverride_ClearRemovesTheOverrideRow()
    {
        var markup = InstanceConfigureMarkup;
        Assert.Contains("ClearListOverride", markup);
        // Repository-direct delete (the page only edits InstanceConfigure; no new
        // server method) — same pattern as native-alarm-source overrides.
        Assert.Contains("DeleteInstanceAttributeOverrideAsync", markup);
        Assert.Contains("HasOverrideRow", markup);
    }

    [Fact]
    public void NonListOverride_KeepsSingleInputUx()
    {
        var markup = InstanceConfigureMarkup;
        // The scalar path still binds the single text input via the existing helpers.
        Assert.Contains("GetOverrideValue(attr.Name)", markup);
        Assert.Contains("OnOverrideChanged(attr.Name, e)", markup);
    }

    [Fact]
    public void EncodedRows_RoundTripThroughCodec_AsThePageDoes()
    {
        // Mirrors the save (Encode rows → JSON) / load (Decode JSON → rows) cycle
        // the page runs. The editor's working rows are always strings
        // (GetListRows → List<string>), so the page encodes a List<string>; the
        // element type is fixed by the base attribute (Int32 here).
        var rows = new List<string> { "10", "20", "30" };
        var json = AttributeValueCodec.Encode(rows);

        // Decode validates the rows against the element type and yields a typed list.
        var decoded = AttributeValueCodec.Decode(json, DataType.List, DataType.Int32);
        var list = Assert.IsType<List<int>>(decoded);
        Assert.Equal(new[] { 10, 20, 30 }, list);

        // The page reloads by decoding then re-encoding each element back to a
        // string row (DecodeListRows), then re-saves by encoding those string rows.
        // That full page round-trip is stable, so a clean override is lossless.
        var reloadedRows = list.Select(x => AttributeValueCodec.Encode(x) ?? string.Empty).ToList();
        Assert.Equal(rows, reloadedRows);
        var reEncoded = AttributeValueCodec.Encode(reloadedRows);
        Assert.Equal(json, reEncoded);
    }

    [Fact]
    public void MalformedListElement_SurfacesFormatException_ForInlineError()
    {
        // The pre-submit guard catches this and shows it inline rather than crashing.
        var json = AttributeValueCodec.Encode(new List<string> { "1", "not-a-number" });
        Assert.Throws<FormatException>(
            () => AttributeValueCodec.Decode(json, DataType.List, DataType.Int32));
    }
}
