using HAI_Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using OmniLinkBridge.Modules;
using OmniLinkBridge.MQTT;
using OmniLinkBridgeTest.Mock;
using Serilog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace OmniLinkBridgeTest
{
    [TestClass]
    public class MQTTTest
    {
        MockOmniLinkII omniLink;
        MessageProcessor messageProcessor;

        [TestInitialize]
        public void Initialize()
        {
            string log_format = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{SourceContext} {Level:u3}] {Message:lj}{NewLine}{Exception}";

            var log_config = new LoggerConfiguration()
                .MinimumLevel.Verbose()
                .Enrich.FromLogContext()
                .WriteTo.Console(outputTemplate: log_format);

            Log.Logger = log_config.CreateLogger();

            Dictionary<string, int> audioSources = new Dictionary<string, int>
            {
                { "Radio", 1 },
                { "Streaming", 2 },
                { "TV", 4 }
            };

            omniLink = new MockOmniLinkII();
            messageProcessor = new MessageProcessor(omniLink, audioSources, 8);

            omniLink.Controller.Units[395].Type = enuOL2UnitType.Flag;
        }

        [TestMethod]
        public void AreaCommandInvalid()
        {
            SendCommandEventArgs actual = null;
            omniLink.OnSendCommand += (sender, e) => { actual = e; };

            // Invalid command
            messageProcessor.Process($"omnilink/area1/command", "disarmed");
            Assert.IsNull(actual);

            // Out of range
            messageProcessor.Process($"omnilink/area9/command", "disarm");
            Assert.IsNull(actual);
        }

        [TestMethod]
        public void AreaCommand()
        {
            void check(ushort id, int code, string payload, enuUnitCommand command)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/area{id}/command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)code,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            // Standard format
            check(1, 0, "disarm", enuUnitCommand.SecurityOff);
            check(1, 0, "arm_home", enuUnitCommand.SecurityDay);
            check(1, 0, "arm_away", enuUnitCommand.SecurityAway);
            check(1, 0, "arm_night", enuUnitCommand.SecurityNight);
            check(1, 0, "arm_home_instant", enuUnitCommand.SecurityDyi);
            check(1, 0, "arm_night_delay", enuUnitCommand.SecurityNtd);
            check(1, 0, "arm_vacation", enuUnitCommand.SecurityVac);

            // Check all areas
            check(0, 0, "disarm", enuUnitCommand.SecurityOff);

            // Check with optional code
            check(1, 1, "disarm,1", enuUnitCommand.SecurityOff);

            // Check case insensitivity
            check(8, 0, "DISARM", enuUnitCommand.SecurityOff);
        }

        [TestMethod]
        public void ZoneCommand()
        {
            void check(ushort id, int code, string payload, enuUnitCommand command, bool ensureNull = false)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/zone{id}/command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)code,
                    Pr2 = id
                };

                if (ensureNull)
                    Assert.IsNull(actual);
                else
                    Assert.AreEqual(expected, actual);
            }

            // Standard format
            check(1, 0, "bypass", enuUnitCommand.Bypass);
            check(1, 0, "restore", enuUnitCommand.Restore);

            // Check all zones
            check(0, 0, "restore", enuUnitCommand.Restore);

            // Not allowed to bypass all zones
            check(0, 0, "bypass", enuUnitCommand.Bypass, true);

            // Check with optional code
            check(1, 1, "bypass,1", enuUnitCommand.Bypass);

            // Check case insensitivity
            check(2, 0, "BYPASS", enuUnitCommand.Bypass);
        }

        [TestMethod]
        public void UnitCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/unit{id}/command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = 0,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "ON", enuUnitCommand.On);
            omniLink.Controller.Units[1].Status = 1;
            check(1, "OFF", enuUnitCommand.Off);

            check(2, "on", enuUnitCommand.On);
        }

        [TestMethod]
        public void UnitFlagCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, int value)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/unit{id}/flag_command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)value,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(395, "0", enuUnitCommand.Set, 0);
            check(395, "1", enuUnitCommand.Set, 1);
            check(395, "255", enuUnitCommand.Set, 255);
        }

        [TestMethod]
        public void UnitLevelCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, int level)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/unit{id}/brightness_command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)level,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "50", enuUnitCommand.Level, 50);
        }

        [TestMethod]
        public void ThermostatModeCommandInvalid()
        {
            SendCommandEventArgs actual = null;
            omniLink.OnSendCommand += (sender, e) => { actual = e; };

            omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatCool;
            messageProcessor.Process($"omnilink/thermostat1/mode_command", "auto");
            Assert.IsNull(actual);

            omniLink.Controller.Thermostats[1].Type = enuThermostatType.CoolOnly;
            messageProcessor.Process($"omnilink/thermostat1/mode_command", "auto");
            Assert.IsNull(actual);
            messageProcessor.Process($"omnilink/thermostat1/mode_command", "heat");
            Assert.IsNull(actual);

            omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatOnly;
            messageProcessor.Process($"omnilink/thermostat1/mode_command", "auto");
            Assert.IsNull(actual);
            messageProcessor.Process($"omnilink/thermostat1/mode_command", "cool");
            Assert.IsNull(actual);
        }

        [TestMethod]
        public void ThermostatModeCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, int mode)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/thermostat{id}/mode_command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)mode,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            omniLink.Controller.Thermostats[1].Type = enuThermostatType.AutoHeatCool;

            check(1, "auto", enuUnitCommand.Mode, (int)enuThermostatMode.Auto);
            check(1, "cool", enuUnitCommand.Mode, (int)enuThermostatMode.Cool);
            check(1, "heat", enuUnitCommand.Mode, (int)enuThermostatMode.Heat);
            check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);

            omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatCool;

            check(1, "cool", enuUnitCommand.Mode, (int)enuThermostatMode.Cool);
            check(1, "heat", enuUnitCommand.Mode, (int)enuThermostatMode.Heat);
            check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);

            omniLink.Controller.Thermostats[1].Type = enuThermostatType.CoolOnly;

            check(1, "cool", enuUnitCommand.Mode, (int)enuThermostatMode.Cool);
            check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);

            omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatOnly;

            check(1, "heat", enuUnitCommand.Mode, (int)enuThermostatMode.Heat);
            check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);
        }

        [TestMethod]
        public void ButtonCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/button{id}/command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = 0,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "ON", enuUnitCommand.Button);
            check(1, "on", enuUnitCommand.Button);
        }

        [TestMethod]
        public void MessageCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, byte par)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/message{id}/command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = par,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "show", enuUnitCommand.ShowMsgWBeep, 0);
            check(1, "show_no_beep", enuUnitCommand.ShowMsgNoBeep, 1);
            check(1, "show_no_beep_or_led", enuUnitCommand.ShowMsgNoBeep, 2);
            check(1, "clear", enuUnitCommand.ClearMsg, 0);

            check(2, "SHOW", enuUnitCommand.ShowMsgWBeep, 0);
        }

        [TestMethod]
        public void LockCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/lock{id}/command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = 0,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "lock", enuUnitCommand.Lock);
            check(1, "unlock", enuUnitCommand.Unlock);

            // Check all locks
            check(0, "lock", enuUnitCommand.Lock);

            // Check case insensitivity
            check(2, "LOCK", enuUnitCommand.Lock);
        }

        [TestMethod]
        public void AudioCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, int value)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/audio{id}/command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)value,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "ON", enuUnitCommand.AudioZone, 1);
            check(1, "OFF", enuUnitCommand.AudioZone, 0);

            check(2, "on", enuUnitCommand.AudioZone, 1);
        }

        [TestMethod]
        public void AudioMuteCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, int value)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/audio{id}/mute_command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)value,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "ON", enuUnitCommand.AudioZone, 3);
            check(1, "OFF", enuUnitCommand.AudioZone, 2);

            Global.mqtt_audio_local_mute = true;
            omniLink.Controller.AudioZones[2].Volume = 50;

            check(2, "on", enuUnitCommand.AudioVolume, 0);
            check(2, "off", enuUnitCommand.AudioVolume, 50);

            omniLink.Controller.AudioZones[2].Volume = 0;

            check(2, "on", enuUnitCommand.AudioVolume, 0);
            check(2, "off", enuUnitCommand.AudioVolume, 10);
        }

        [TestMethod]
        public void AudioSourceCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, int value)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/audio{id}/source_command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)value,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "Radio", enuUnitCommand.AudioSource, 1);
            check(1, "Streaming", enuUnitCommand.AudioSource, 2);

            check(2, "TV", enuUnitCommand.AudioSource, 4);
        }

        [TestMethod]
        public void AudioVolumeCommand()
        {
            void check(ushort id, string payload, enuUnitCommand command, int value)
            {
                SendCommandEventArgs actual = null;
                omniLink.OnSendCommand += (sender, e) => { actual = e; };
                messageProcessor.Process($"omnilink/audio{id}/volume_command", payload);
                SendCommandEventArgs expected = new SendCommandEventArgs()
                {
                    Cmd = command,
                    Par = (byte)value,
                    Pr2 = id
                };
                Assert.AreEqual(expected, actual);
            }

            check(1, "100", enuUnitCommand.AudioVolume, 100);
            check(1, "75", enuUnitCommand.AudioVolume, 75);

            check(2, "0", enuUnitCommand.AudioVolume, 0);

            Global.mqtt_audio_volume_media_player = true;

            check(2, "1", enuUnitCommand.AudioVolume, 100);
            check(2, "0.75", enuUnitCommand.AudioVolume, 75);
            check(2, "0", enuUnitCommand.AudioVolume, 0);
        }
    }
}