using System;
using System.Collections.Generic;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Worker.Conversion;

namespace ZB.MOM.WW.MxGateway.Worker.Tests.Conversion;

public sealed class MxStatusProxyConverterTests
{
    private readonly MxStatusProxyConverter _converter = new();

    /// <summary>Verifies that status struct fields are preserved during conversion.</summary>
    [Fact]
    public void Convert_WithStatusStruct_PreservesStatusFields()
    {
        FakeMxStatusProxy status = new()
        {
            success = 1,
            category = 5,
            detectedBy = 3,
            detail = 21,
        };

        MxStatusProxy converted = _converter.Convert(status);

        Assert.Equal(1, converted.Success);
        Assert.Equal(MxStatusCategory.OperationalError, converted.Category);
        Assert.Equal(MxStatusSource.RespondingNmx, converted.DetectedBy);
        Assert.Equal(21, converted.Detail);
        Assert.Equal(5, converted.RawCategory);
        Assert.Equal(3, converted.RawDetectedBy);
        Assert.Equal("Invalid reference", converted.DiagnosticText);
    }

    /// <summary>Verifies that status arrays are converted without collapsing duplicate entries.</summary>
    [Fact]
    public void ConvertMany_WithStatusArray_DoesNotCollapseEntries()
    {
        FakeMxStatusProxy[] statuses =
        [
            new()
            {
                success = 1,
                category = 0,
                detectedBy = 0,
                detail = 0,
            },
            new()
            {
                success = 0,
                category = 6,
                detectedBy = 5,
                detail = 33,
            },
        ];

        IReadOnlyList<MxStatusProxy> converted = _converter.ConvertMany(statuses);

        Assert.Equal(2, converted.Count);
        Assert.Equal(MxStatusCategory.Ok, converted[0].Category);
        Assert.Equal(MxStatusCategory.SecurityError, converted[1].Category);
        Assert.Equal(MxStatusSource.RespondingAutomationObject, converted[1].DetectedBy);
        Assert.Equal("Write access denied", converted[1].DiagnosticText);
    }

    /// <summary>Verifies that unknown category and source values preserve raw field values.</summary>
    [Fact]
    public void Convert_WithUnknownCategoryAndSource_PreservesRawFields()
    {
        FakeMxStatusProxy status = new()
        {
            success = -1,
            category = 99,
            detectedBy = 42,
            detail = 1234,
        };

        MxStatusProxy converted = _converter.Convert(status);

        Assert.Equal(-1, converted.Success);
        Assert.Equal(MxStatusCategory.Unknown, converted.Category);
        Assert.Equal(MxStatusSource.Unknown, converted.DetectedBy);
        Assert.Equal(99, converted.RawCategory);
        Assert.Equal(42, converted.RawDetectedBy);
        Assert.Equal(1234, converted.Detail);
        Assert.Equal(string.Empty, converted.DiagnosticText);
    }

    /// <summary>Verifies that completion-only status bytes are preserved as hex metadata.</summary>
    [Fact]
    public void PreserveCompletionOnlyStatusBytes_ReturnsRawHexMetadata()
    {
        string rawStatus = _converter.PreserveCompletionOnlyStatusBytes(
            [0x00, 0x00, 0x50, 0x80, 0x00]);

        Assert.Equal("completion_only_status_hex=0000508000", rawStatus);
    }

    /// <summary>Verifies that missing status fields throw a conversion exception.</summary>
    [Fact]
    public void Convert_WithMissingStatusField_ThrowsConversionException()
    {
        MxStatusConversionException exception =
            Assert.Throws<MxStatusConversionException>(() => _converter.Convert(new MissingFields()));

        Assert.Contains("success", exception.Message);
    }

    /// <summary>
    ///     Verifies that repeated conversions of the same status type produce
    ///     identical results. WRK-06 caches the resolved FieldInfo objects per
    ///     type after the first conversion; the cached path must yield the same
    ///     message the uncached first call produced.
    /// </summary>
    [Fact]
    public void Convert_RepeatedForSameType_ProducesIdenticalResults()
    {
        FakeMxStatusProxy status = new()
        {
            success = 0,
            category = 3,
            detectedBy = 1,
            detail = 21,
        };

        // First call populates the per-type FieldInfo cache; the second reuses it.
        MxStatusProxy first = _converter.Convert(status);
        MxStatusProxy second = _converter.Convert(status);

        Assert.Equal(first, second);
        Assert.Equal(MxStatusCategory.CommunicationError, second.Category);
        Assert.Equal(MxStatusSource.RespondingLmx, second.DetectedBy);
        Assert.Equal(0, second.Success);
        Assert.Equal(21, second.Detail);
        Assert.Equal("Invalid reference", second.DiagnosticText);
    }

    /// <summary>
    ///     Verifies every accessor strategy converts the same status values
    ///     identically. PERF-20 reads a public field of a publicly visible type
    ///     through a delegate compiled from an expression tree instead of
    ///     <c>FieldInfo.GetValue</c>; a field type whose conversion to int is not
    ///     a lossless widening (<see cref="FakeWideStatusProxy"/>) and a type the
    ///     compiled delegate may not touch (<see cref="HiddenStatusProxy"/>, not
    ///     visible outside this assembly) keep the reflection read. All four
    ///     doubles below carry the same logical status, so all four messages must
    ///     be equal.
    /// </summary>
    [Fact]
    public void Convert_AcrossAccessorStrategies_ProducesIdenticalMessages()
    {
        // Struct, widening field types: the compiled accessor unboxes in place.
        MxStatusProxy compiledStruct = _converter.Convert(new FakeMxStatusProxy
        {
            success = 1,
            category = 5,
            detectedBy = 3,
            detail = 21,
        });

        // Reference type, widening field types: the compiled accessor casts.
        MxStatusProxy compiledClass = _converter.Convert(new FakeStatusProxyClass
        {
            success = 1,
            category = 5,
            detectedBy = 3,
            detail = 21,
        });

        // Non-widening field types: reflection + Convert.ToInt32 is kept.
        MxStatusProxy reflectedWide = _converter.Convert(new FakeWideStatusProxy
        {
            success = 1L,
            category = 5L,
            detectedBy = 3L,
            detail = 21L,
        });

        // Type not visible outside the assembly: reflection is kept.
        MxStatusProxy reflectedHidden = _converter.Convert(new HiddenStatusProxy
        {
            success = 1,
            category = 5,
            detectedBy = 3,
            detail = 21,
        });

        Assert.Equal(compiledStruct, compiledClass);
        Assert.Equal(compiledStruct, reflectedWide);
        Assert.Equal(compiledStruct, reflectedHidden);
        Assert.Equal(MxStatusCategory.OperationalError, compiledStruct.Category);
        Assert.Equal(MxStatusSource.RespondingNmx, compiledStruct.DetectedBy);
        Assert.Equal("Invalid reference", compiledStruct.DiagnosticText);
    }

    /// <summary>
    ///     Verifies the compiled accessor widens a negative 16-bit field the way
    ///     <c>Convert.ToInt32</c> did — sign-extended, not reinterpreted. The
    ///     interop MXSTATUS_PROXY declares 16-bit fields, so a sign-extension
    ///     mistake here would silently change every failing status the gateway
    ///     reports.
    /// </summary>
    [Fact]
    public void Convert_WithExtremeInt16FieldValues_SignExtendsLikeConvertToInt32()
    {
        FakeMxStatusProxy status = new()
        {
            success = short.MinValue,
            category = 2,
            detectedBy = 0,
            detail = short.MaxValue,
        };

        MxStatusProxy converted = _converter.Convert(status);

        Assert.Equal((int)short.MinValue, converted.Success);
        Assert.Equal((int)short.MaxValue, converted.Detail);
        Assert.Equal(MxStatusCategory.Warning, converted.Category);
        Assert.Equal(MxStatusSource.RequestingLmx, converted.DetectedBy);
    }

    public struct FakeMxStatusProxy
    {
        public short success;

        public int category;

        public int detectedBy;

        public short detail;
    }

    public struct FakeWideStatusProxy
    {
        public long success;

        public long category;

        public long detectedBy;

        public long detail;
    }

    public sealed class FakeStatusProxyClass
    {
        public int success;

        public int category;

        public int detectedBy;

        public int detail;
    }

    private struct HiddenStatusProxy
    {
        public short success;

        public int category;

        public int detectedBy;

        public short detail;
    }

    private sealed class MissingFields
    {
    }
}
