using System.Collections.Generic;

namespace ZB.MOM.WW.LmxProxy.Host.Domain
{
    /// <summary>
    ///     Maps between the domain <see cref="Quality"/> enum and proto QualityCode messages.
    ///     status_code (uint32) is canonical. symbolic_name is derived from a lookup table.
    /// </summary>
    public static class QualityCodeMapper
    {
        /// <summary>OPC UA status code → symbolic name lookup.</summary>
        private static readonly Dictionary<uint, string> StatusCodeToName = new Dictionary<uint, string>
        {
            // Good
            { 0x00000000, "Good" },
            { 0x00D80000, "GoodLocalOverride" },

            // Uncertain
            { 0x40900000, "UncertainLastUsableValue" },
            { 0x42390000, "UncertainSensorNotAccurate" },
            { 0x40540000, "UncertainEngineeringUnitsExceeded" },
            { 0x40580000, "UncertainSubNormal" },

            // Bad
            { 0x80000000, "Bad" },
            { 0x80040000, "BadConfigurationError" },
            { 0x808A0000, "BadNotConnected" },
            { 0x806B0000, "BadDeviceFailure" },
            { 0x806D0000, "BadSensorFailure" },
            { 0x80050000, "BadCommunicationFailure" },
            { 0x808F0000, "BadOutOfService" },
            { 0x80320000, "BadWaitingForInitialData" },
        };

        /// <summary>Domain Quality enum → OPC UA status code.</summary>
        private static readonly Dictionary<Quality, uint> QualityToStatusCode = new Dictionary<Quality, uint>
        {
            // Good family
            { Quality.Good, 0x00000000 },
            { Quality.Good_LowLimited, 0x00000000 },
            { Quality.Good_HighLimited, 0x00000000 },
            { Quality.Good_Constant, 0x00000000 },
            { Quality.Good_LocalOverride, 0x00D80000 },
            { Quality.Good_LocalOverride_LL, 0x00D80000 },
            { Quality.Good_LocalOverride_HL, 0x00D80000 },
            { Quality.Good_LocalOverride_C, 0x00D80000 },

            // Uncertain family
            { Quality.Uncertain, 0x40900000 },
            { Quality.Uncertain_LowLimited, 0x40900000 },
            { Quality.Uncertain_HighLimited, 0x40900000 },
            { Quality.Uncertain_Constant, 0x40900000 },
            { Quality.Uncertain_LastUsable, 0x40900000 },
            { Quality.Uncertain_LastUsable_LL, 0x40900000 },
            { Quality.Uncertain_LastUsable_HL, 0x40900000 },
            { Quality.Uncertain_LastUsable_Cnst, 0x40900000 },
            { Quality.Uncertain_SensorNotAcc, 0x42390000 },
            { Quality.Uncertain_SensorNotAcc_LL, 0x42390000 },
            { Quality.Uncertain_SensorNotAcc_HL, 0x42390000 },
            { Quality.Uncertain_SensorNotAcc_C, 0x42390000 },
            { Quality.Uncertain_EuExceeded, 0x40540000 },
            { Quality.Uncertain_EuExceeded_LL, 0x40540000 },
            { Quality.Uncertain_EuExceeded_HL, 0x40540000 },
            { Quality.Uncertain_EuExceeded_C, 0x40540000 },
            { Quality.Uncertain_SubNormal, 0x40580000 },
            { Quality.Uncertain_SubNormal_LL, 0x40580000 },
            { Quality.Uncertain_SubNormal_HL, 0x40580000 },
            { Quality.Uncertain_SubNormal_C, 0x40580000 },

            // Bad family
            { Quality.Bad, 0x80000000 },
            { Quality.Unknown, 0x80000000 },
            { Quality.Bad_ConfigError, 0x80040000 },
            { Quality.Bad_NotConnected, 0x808A0000 },
            { Quality.Bad_DeviceFailure, 0x806B0000 },
            { Quality.Bad_SensorFailure, 0x806D0000 },
            { Quality.Bad_LastKnownValue, 0x80050000 },
            { Quality.Bad_CommFailure, 0x80050000 },
            { Quality.Bad_OutOfService, 0x808F0000 },
            { Quality.Bad_WaitingForInitialData, 0x80320000 },
        };

        /// <summary>
        ///     Converts a domain Quality enum to a proto QualityCode message.
        /// </summary>
        public static Scada.QualityCode ToQualityCode(Quality quality)
        {
            var statusCode = QualityToStatusCode.TryGetValue(quality, out var code) ? code : 0x80000000u;
            var symbolicName = StatusCodeToName.TryGetValue(statusCode, out var name) ? name : "Bad";

            return new Scada.QualityCode
            {
                StatusCode = statusCode,
                SymbolicName = symbolicName
            };
        }

        /// <summary>OPC UA status code → primary domain Quality (reverse lookup).</summary>
        private static readonly Dictionary<uint, Quality> StatusCodeToQuality = new Dictionary<uint, Quality>
        {
            // Good
            { 0x00000000, Quality.Good },
            { 0x00D80000, Quality.Good_LocalOverride },

            // Uncertain — pick the most specific base variant
            { 0x40900000, Quality.Uncertain_LastUsable },
            { 0x42390000, Quality.Uncertain_SensorNotAcc },
            { 0x40540000, Quality.Uncertain_EuExceeded },
            { 0x40580000, Quality.Uncertain_SubNormal },

            // Bad
            { 0x80000000, Quality.Bad },
            { 0x80040000, Quality.Bad_ConfigError },
            { 0x808A0000, Quality.Bad_NotConnected },
            { 0x806B0000, Quality.Bad_DeviceFailure },
            { 0x806D0000, Quality.Bad_SensorFailure },
            { 0x80050000, Quality.Bad_CommFailure },
            { 0x808F0000, Quality.Bad_OutOfService },
            { 0x80320000, Quality.Bad_WaitingForInitialData },
        };

        /// <summary>
        ///     Converts an OPC UA status code (uint32) to a domain Quality enum.
        ///     Falls back to the nearest category if the specific code is not mapped.
        /// </summary>
        public static Quality FromStatusCode(uint statusCode)
        {
            if (StatusCodeToQuality.TryGetValue(statusCode, out var quality))
                return quality;

            // Category fallback
            uint category = statusCode & 0xC0000000;
            if (category == 0x00000000) return Quality.Good;
            if (category == 0x40000000) return Quality.Uncertain;
            return Quality.Bad;
        }

        /// <summary>
        ///     Gets the symbolic name for a status code.
        /// </summary>
        public static string GetSymbolicName(uint statusCode)
        {
            if (StatusCodeToName.TryGetValue(statusCode, out var name))
                return name;

            uint category = statusCode & 0xC0000000;
            if (category == 0x00000000) return "Good";
            if (category == 0x40000000) return "Uncertain";
            return "Bad";
        }

        /// <summary>
        ///     Creates a QualityCode for a specific well-known status.
        /// </summary>
        public static Scada.QualityCode Good() => new Scada.QualityCode { StatusCode = 0x00000000, SymbolicName = "Good" };
        public static Scada.QualityCode Bad() => new Scada.QualityCode { StatusCode = 0x80000000, SymbolicName = "Bad" };
        public static Scada.QualityCode BadConfigurationError() => new Scada.QualityCode { StatusCode = 0x80040000, SymbolicName = "BadConfigurationError" };
        public static Scada.QualityCode BadCommunicationFailure() => new Scada.QualityCode { StatusCode = 0x80050000, SymbolicName = "BadCommunicationFailure" };
        public static Scada.QualityCode BadNotConnected() => new Scada.QualityCode { StatusCode = 0x808A0000, SymbolicName = "BadNotConnected" };
        public static Scada.QualityCode BadDeviceFailure() => new Scada.QualityCode { StatusCode = 0x806B0000, SymbolicName = "BadDeviceFailure" };
        public static Scada.QualityCode BadSensorFailure() => new Scada.QualityCode { StatusCode = 0x806D0000, SymbolicName = "BadSensorFailure" };
        public static Scada.QualityCode BadOutOfService() => new Scada.QualityCode { StatusCode = 0x808F0000, SymbolicName = "BadOutOfService" };
        public static Scada.QualityCode BadWaitingForInitialData() => new Scada.QualityCode { StatusCode = 0x80320000, SymbolicName = "BadWaitingForInitialData" };
        public static Scada.QualityCode GoodLocalOverride() => new Scada.QualityCode { StatusCode = 0x00D80000, SymbolicName = "GoodLocalOverride" };
        public static Scada.QualityCode UncertainLastUsableValue() => new Scada.QualityCode { StatusCode = 0x40900000, SymbolicName = "UncertainLastUsableValue" };
    }
}
