using Elwig.Helpers.Weighing;

namespace Tests.WeighingTests {
    [TestFixture]
    class ScaleTestWolkersdorfIT6000E {

        private MockScale? MockA;
        private MockScale? MockB;
        private SysTecITScale? ScaleA;
        private SysTecITScale? ScaleB;

        private static (string, bool) ScaleHandler(string req, int weight, string? error, int identNr, string terminalNr) {
            var modes = error?.Split(';') ?? [];
            var overloaded = modes.Contains("overloaded");
            var moving = modes.Contains("moving");
            var invalid = modes.Contains("invalid");
            var crc = modes.Contains("crc");
            var unit = modes.Contains("unit");

            if (invalid) {
                return ("abcd\r\n", false);
            } else if (!req.StartsWith('<') || !req.EndsWith('>')) {
                return ("<31>\r\n", false);
            }
            req = req[1..^1];

            bool incr;
            if (req.Length > 3) {
                return ("<32>\r\n", false);
            } else if (req.StartsWith("RN")) {
                incr = true;
            } else if (req.StartsWith("RM")) {
                incr = false;
            } else {
                return ("<32>\r\n", false);
            }

            if (overloaded)
                return ("<12>\r\n", false);

            if (moving && incr)
                return ("<13>\r\n", false);

            string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 8, 8, 47, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
                $"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")}   {terminalNr,3}";
            ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
            if (crc) checksum += 10;
            return ($"<{data}{checksum,8}>\r\n", incr);
        }

        [OneTimeSetUp]
        public void SetupScale() {
            MockA = new CommandMockScale(12345, (req, weight, error, identNr) => ScaleHandler(req, weight, error, identNr, "A"));
            MockB = new CommandMockScale(12346, (req, weight, error, identNr) => ScaleHandler(req, weight, error, identNr, "B"));
            ScaleA = new("A", "IT3000E", "tcp://127.0.0.1:12345");
            ScaleB = new("B", "IT3000E", "tcp://127.0.0.1:12346");
        }

        [OneTimeTearDown]
        public void TeardownScale() {
            MockA?.Dispose();
            MockB?.Dispose();
            ScaleA?.Dispose();
            ScaleB?.Dispose();
        }

        [SetUp]
        public void ResetScale() {
            MockA!.IdentNr = 0;
            MockA!.Weight = 0;
            MockA!.Error = null;
            MockB!.IdentNr = 0;
            MockB!.Weight = 0;
            MockB!.Error = null;
        }

        [Test]
        public async Task Test_01_CurrentWeight() {
            MockA!.Weight = 1234;
            Assert.That(await ScaleA!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
                Weight = 1234, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
            MockB!.Weight = 3456;
            Assert.That(await ScaleB!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
                Weight = 3456, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
            MockA!.Weight = 1236;
            Assert.That(await ScaleA!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
                Weight = 1236, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
            MockB!.Weight = 3457;
            Assert.That(await ScaleB!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
                Weight = 3457, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
        }

        [Test]
        public async Task Test_02_Normal() {
            MockA!.Weight = 1234;
            Assert.That(await ScaleA!.Weigh(), Is.EqualTo(new WeighingResult {
                Weight = 1234, WeighingId = "1",
                FullWeighingId = $"2020-10-08/1",
                Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
            MockB!.Weight = 3456;
            Assert.That(await ScaleB!.Weigh(), Is.EqualTo(new WeighingResult {
                Weight = 3456, WeighingId = "1",
                FullWeighingId = $"2020-10-08/1",
                Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
            MockA!.Weight = 4321;
            Assert.That(await ScaleA!.Weigh(), Is.EqualTo(new WeighingResult {
                Weight = 4321, WeighingId = "2",
                FullWeighingId = $"2020-10-08/2",
                Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
            MockB!.Weight = 3333;
            Assert.That(await ScaleB!.Weigh(), Is.EqualTo(new WeighingResult {
                Weight = 3333, WeighingId = "2",
                FullWeighingId = $"2020-10-08/2",
                Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
            }));
        }

        [Test]
        public void Test_03_Moving() {
            MockA!.Weight = 1_000;
            MockA!.Error = "moving";
            IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
            Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
        }

        [Test]
        public void Test_04_Overloaded() {
            MockA!.Weight = 10_000;
            MockA!.Error = "overloaded";
            IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
            Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
        }

        [Test]
        public void Test_05_InvalidResponse() {
            MockA!.Weight = 1_000;
            MockA!.Error = "invalid";
            Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
        }

        [Test]
        public void Test_06_InvalidCrc() {
            MockA!.Weight = 1_000;
            MockA!.Error = "crc";
            IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
            Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
        }

        [Test]
        public void Test_07_InvalidUnit() {
            MockA!.Weight = 1_000;
            MockA!.Error = "unit";
            IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
        }
    }
}