using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using ZB.MOM.WW.MxGateway.Contracts.Proto;

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

/// <summary>Converts MXAccess MXSTATUS_PROXY COM objects to protobuf MxStatusProxy messages.</summary>
public sealed class MxStatusProxyConverter
{
    /// <summary>Converts a single status object to a protobuf message, reflecting all fields and diagnostics.</summary>
    /// <param name="status">COM status object to convert.</param>
    public MxStatusProxy Convert(object status)
    {
        if (status is null)
        {
            throw new ArgumentNullException(nameof(status));
        }

        Type statusType = status.GetType();
        int success = ReadInt32Field(status, statusType, "success");
        int rawCategory = ReadInt32Field(status, statusType, "category");
        int rawDetectedBy = ReadInt32Field(status, statusType, "detectedBy");
        int detail = ReadInt32Field(status, statusType, "detail");

        return new MxStatusProxy
        {
            Success = success,
            Category = MapCategory(rawCategory),
            DetectedBy = MapSource(rawDetectedBy),
            Detail = detail,
            RawCategory = rawCategory,
            RawDetectedBy = rawDetectedBy,
            DiagnosticText = MxStatusDetailText.Lookup(detail),
        };
    }

    /// <summary>Converts an array of status objects, handling nulls gracefully.</summary>
    /// <param name="statuses">Array of COM status objects; null returns empty list.</param>
    public IReadOnlyList<MxStatusProxy> ConvertMany(Array? statuses)
    {
        if (statuses is null)
        {
            return Array.Empty<MxStatusProxy>();
        }

        List<MxStatusProxy> converted = new(statuses.Length);
        foreach (object? status in statuses)
        {
            if (status is null)
            {
                converted.Add(new MxStatusProxy
                {
                    Category = MxStatusCategory.Unknown,
                    DetectedBy = MxStatusSource.Unknown,
                    DiagnosticText = "Null MXSTATUS_PROXY entry.",
                });
                continue;
            }

            converted.Add(Convert(status));
        }

        return converted;
    }

    /// <summary>Preserves completion-only status bytes as a diagnostic hex string since they cannot be unpacked.</summary>
    /// <param name="statusBytes">Status bytes to encode as hex string.</param>
    public string PreserveCompletionOnlyStatusBytes(byte[] statusBytes)
    {
        if (statusBytes is null)
        {
            throw new ArgumentNullException(nameof(statusBytes));
        }

        return $"completion_only_status_hex={BitConverter.ToString(statusBytes).Replace("-", string.Empty)}";
    }

    private static int ReadInt32Field(
        object value,
        Type valueType,
        string fieldName)
    {
        FieldInfo? field = valueType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public);
        if (field is null)
        {
            throw new MxStatusConversionException(
                $"Status object type '{valueType.FullName}' does not expose required field '{fieldName}'.");
        }

        object? fieldValue = field.GetValue(value);
        if (fieldValue is null)
        {
            throw new MxStatusConversionException(
                $"Status object field '{fieldName}' on type '{valueType.FullName}' is null.");
        }

        return System.Convert.ToInt32(fieldValue, CultureInfo.InvariantCulture);
    }

    private static MxStatusCategory MapCategory(int rawCategory)
    {
        return rawCategory switch
        {
            -1 => MxStatusCategory.Unknown,
            0 => MxStatusCategory.Ok,
            1 => MxStatusCategory.Pending,
            2 => MxStatusCategory.Warning,
            3 => MxStatusCategory.CommunicationError,
            4 => MxStatusCategory.ConfigurationError,
            5 => MxStatusCategory.OperationalError,
            6 => MxStatusCategory.SecurityError,
            7 => MxStatusCategory.SoftwareError,
            8 => MxStatusCategory.OtherError,
            _ => MxStatusCategory.Unknown,
        };
    }

    private static MxStatusSource MapSource(int rawDetectedBy)
    {
        return rawDetectedBy switch
        {
            -1 => MxStatusSource.Unknown,
            0 => MxStatusSource.RequestingLmx,
            1 => MxStatusSource.RespondingLmx,
            2 => MxStatusSource.RequestingNmx,
            3 => MxStatusSource.RespondingNmx,
            4 => MxStatusSource.RequestingAutomationObject,
            5 => MxStatusSource.RespondingAutomationObject,
            _ => MxStatusSource.Unknown,
        };
    }
}
