using Application.ATG_Classic_App;
using Application.ATG_Classic_App.Model;
using AutoMapper;
using Edge.Core.Processor;
using Edge.Core.IndustryStandardInterface.Pump;
using Edge.Core.IndustryStandardInterface.ATG;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using Edge.Core.UniversalApi;

namespace Application.ATG_Classic_App_Test
{
    [TestClass]
    public class ATG
    {
        static int TankCount = 6;
        //ServiceProvider serviceProvider;
        static List<MockProbeHandler> probeHandlers;
        static ATG_Classic_App.App atg;
        static List<TankProfileData> sharedTankProfileDatas;
        static IEnumerable<TankConfig> tankConfigs;
        static TankOverallConfig tankOverallConfig;
        static int InventorySamplingInterval = 5000;

        [ClassInitialize]
        public static void ClassInit(TestContext context)
        {
            var services = new ServiceCollection();
            services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);

            #region AutoMapper, resolve all assemblies under entry folder.

            var applicationAssFileInfos = new DirectoryInfo(Directory.GetCurrentDirectory())
                        .GetFiles().Where(f => //f.Name.ToLower().StartsWith("application") &&
                            f.Extension != null
                            && (f.Extension.ToLower() == ".dll" || f.Extension.ToLower() == ".exe"));
            var assembliesWithAutoMapperProfileDefined = new List<Assembly>();
            foreach (var ai in applicationAssFileInfos)
            {
                try
                {
                    var ass = Assembly.LoadFrom(ai.FullName);
                    if (ass.GetTypes().Any(t => typeof(Profile).IsAssignableFrom(t)))
                        assembliesWithAutoMapperProfileDefined.Add(ass);
                }
                catch
                { }
            }

            services.AddAutoMapper(assembliesWithAutoMapperProfileDefined);

            services.AddSingleton(new UniversalApiHub(null, null));

            #endregion

            var serviceProvider = services.BuildServiceProvider();

            probeHandlers = Enumerable.Range(1, TankCount).Select(i => new MockProbeHandler(i, i * 1000)).ToList();
            sharedTankProfileDatas = Enumerable.Range(0, 2001).Select(i =>
                new TankProfileData()
                {
                    BatchLabel = "shitday",
                    Height = i,
                    Volume = i * 2
                }).ToList();
            tankConfigs = Enumerable.Range(1, TankCount).Select(i => new TankConfig()
            {
                TankNumber = (byte)i,
                Label = "I'm Tank with Number " + i,
                Diameter = i * 1000,
                ProbeConfig = new ProbeConfig() { DeviceId = i },
                ProductConfig = new ProductConfig() { ProductCode = (i + 90).ToString(), ProductLabel = (i + 90).ToString() + "#" },
                TankLimitConfig = new TankLimitConfig()
                {
                    FullVolume = 1200 * i,
                    MaxVolume = 1100 * i,
                    HighProduct = 900 * i,
                    LowProduct = 100 * i,
                    HighWaterWarning = 10 * i,
                    HighWaterAlarm = 40 * i,
                    FuelTemperatureLowLimit = i,
                    FuelTemperatureHighLimit = 2 * i
                },
            });
            tankOverallConfig = new TankOverallConfig() { TcReference = 25, InventorySamplingInterval = InventorySamplingInterval };


            var dbContextFactory = new DefaultDbContextFactory();
            dbContextFactory.AddProvider(new InMemoryDbContextProvider());

            atg = new ATG_Classic_App.App(serviceProvider, 1, true);
            atg.DbContextFactory = dbContextFactory;
            atg.Init(probeHandlers.Cast<IProcessor>());

            var _ = atg.UpsertConfigAsync(tankOverallConfig).Result;
            foreach (var tc in tankConfigs)
            {
                var xx = atg.UpsertConfigAsync(tc).Result;
            }

            foreach (var tc in tankConfigs)
            {
                var xx = atg.AddTankProfileDataAsync(
                    tc.TankNumber,
                    sharedTankProfileDatas.Select(d => d.Height.ToString() + ":" + d.Volume.ToString()).Aggregate((n, acc) => n + ", " + acc)
                    ).Result;
            }

            atg.Start().Wait();
        }

        class InMemoryDbContextProvider : IDbContextProvider
        {
            public T CreateDbContext<T>() where T : DbContext
            {
                //var options = new DbContextOptionsBuilder<InMemoryDbContext>()
                //   .UseInMemoryDatabase(databaseName: "Test")
                //   .Options;
                return new InMemoryDbContext() as T;
            }

            public void Dispose()
            {
                //throw new NotImplementedException();
            }
        }

        [TestInitialize]
        public void TestMethodInit()
        {

        }

        [TestMethod]
        public async Task TankReading_TestMethod1()
        {
            var tanks = atg.Tanks;
            var mockReading = new Edge.Core.IndustryStandardInterface.ATG.ProbeReading()
            {
                Height = 1322,
                Water = 123,
                Temperature = new[] { (double)41 }
            };

            probeHandlers.ForEach(ph => ph.Mock_SetProbeReadingValueProducer((_) => mockReading));
            Assert.AreEqual(true, atg.Tanks != null);
            Assert.AreEqual(true, atg.Tanks.Count() == TankCount);

            await Task.Delay(atg.polling_fast_TankReadingTimer_Internval);
            var volumeCaculatorTester = new HeightToVolumeCaculator(sharedTankProfileDatas);
            foreach (var tank in atg.Tanks)
            {
                var tankConfig = tankConfigs.First(tc => tc.TankNumber == tank.TankNumber);
                var tcCaculator = new TemperatureCompensationCaculator(tankConfig.ThermalCoefficient);

                var tankReading = await atg.GetTankReadingAsync(tank.TankNumber);
                Assert.AreEqual(true, tankReading != null);
                Assert.AreEqual(true, tankReading.Height == mockReading.Height);
                Assert.AreEqual(true, tankReading.Water == mockReading.Water);
                Assert.AreEqual(true, tankReading.Temperature.Value == mockReading.Temperature.First());

                Assert.AreEqual(tankReading.TcVolume,
                    tcCaculator.CaculateCompensatedVolume(tankOverallConfig.TcReference, tankReading.Volume.Value, tankReading.Temperature.Value));
                Assert.AreEqual(true, tankReading.WaterVolume == volumeCaculatorTester.GetVolume(tankReading.Water.Value));
                Assert.AreEqual(true, tankReading.Volume == volumeCaculatorTester.GetVolume(tankReading.Height.Value) - tankReading.WaterVolume);
                Assert.AreEqual(tankReading.Ullage,
                     volumeCaculatorTester.GetVolume(sharedTankProfileDatas.Max(p => p.Height)) - tankReading.Volume - tankReading.WaterVolume);
            }
        }

        [TestMethod]
        public async Task TankReading_TestMethod2()
        {
            var tanks = atg.Tanks;
            Func<IProbeHandler, ProbeReading> mockReadingProducer = (ph) =>
                new ProbeReading()
                {
                    Height = ph.Probe.ProbeLength * 0.8,
                    Water = ph.Probe.ProbeLength * 0.1,
                    Temperature = new[] { (double)(49 + ph.Probe.ProbeLength) }
                };

            probeHandlers.ForEach(ph => ph.Mock_SetProbeReadingValueProducer(mockReadingProducer));
            Assert.AreEqual(true, atg.Tanks != null);
            Assert.AreEqual(true, atg.Tanks.Count() == TankCount);

            await Task.Delay(atg.polling_fast_TankReadingTimer_Internval * 2);
            var volumeCaculatorTester = new HeightToVolumeCaculator(sharedTankProfileDatas);
            foreach (var tank in atg.Tanks)
            {
                var tankConfig = tankConfigs.First(tc => tc.TankNumber == tank.TankNumber);
                var tcCaculator = new TemperatureCompensationCaculator(tankConfig.ThermalCoefficient);

                var tankReading = await atg.GetTankReadingAsync(tank.TankNumber);
                Assert.AreEqual(true, tankReading != null);
                Assert.AreEqual(true, tankReading.Height == tank.Probe.ProbeLength * 0.8);
                Assert.AreEqual(true, tankReading.Water == tank.Probe.ProbeLength * 0.1);
                Assert.AreEqual(true, tankReading.Temperature.Value == (double)(49 + tank.Probe.ProbeLength));

                Assert.AreEqual(tankReading.TcVolume,
                    tcCaculator.CaculateCompensatedVolume(tankOverallConfig.TcReference, tankReading.Volume.Value, tankReading.Temperature.Value));
                Assert.AreEqual(true, tankReading.WaterVolume == volumeCaculatorTester.GetVolume(tankReading.Water.Value));
                Assert.AreEqual(true, tankReading.Volume == volumeCaculatorTester.GetVolume(tankReading.Height.Value) - tankReading.WaterVolume);
                Assert.AreEqual(tankReading.Ullage,
                     volumeCaculatorTester.GetVolume(sharedTankProfileDatas.Max(p => p.Height)) - tankReading.Volume - tankReading.WaterVolume);
            }
        }

        [TestMethod]
        public async Task Inventory_TestMethod1()
        {
            var tanks = atg.Tanks;
            Func<IProbeHandler, ProbeReading> mockReadingProducer = (ph) =>
                new ProbeReading()
                {
                    Height = ph.Probe.ProbeLength * 0.8,
                    Water = ph.Probe.ProbeLength * 0.1,
                    Temperature = new[] { (double)(49 + ph.Probe.ProbeLength) }
                };

            probeHandlers.ForEach(ph => ph.Mock_SetProbeReadingValueProducer(mockReadingProducer));
            Assert.AreEqual(true, atg.Tanks != null);
            Assert.AreEqual(true, atg.Tanks.Count() == TankCount);

            await Task.Delay(InventorySamplingInterval * 3);
            var volumeCaculatorTester = new HeightToVolumeCaculator(sharedTankProfileDatas);
            foreach (var tank in atg.Tanks)
            {
                var tankConfig = tankConfigs.First(tc => tc.TankNumber == tank.TankNumber);
                var tcCaculator = new TemperatureCompensationCaculator(tankConfig.ThermalCoefficient);

                var tankReading = await atg.GetTankReadingAsync(tank.TankNumber);
                Assert.AreEqual(true, tankReading != null);
                Assert.AreEqual(true, tankReading.Height == tank.Probe.ProbeLength * 0.8);
                Assert.AreEqual(true, tankReading.Water == tank.Probe.ProbeLength * 0.1);
                Assert.AreEqual(true, tankReading.Temperature.Value == (double)(49 + tank.Probe.ProbeLength));

                Assert.AreEqual(tankReading.TcVolume,
                    tcCaculator.CaculateCompensatedVolume(tankOverallConfig.TcReference, tankReading.Volume.Value, tankReading.Temperature.Value));
                Assert.AreEqual(true, tankReading.WaterVolume == volumeCaculatorTester.GetVolume(tankReading.Water.Value));
                Assert.AreEqual(true, tankReading.Volume == volumeCaculatorTester.GetVolume(tankReading.Height.Value) - tankReading.WaterVolume);
                Assert.AreEqual(tankReading.Ullage,
                     volumeCaculatorTester.GetVolume(sharedTankProfileDatas.Max(p => p.Height)) - tankReading.Volume - tankReading.WaterVolume);

                var inventories = await atg.GetTankInventoryAsync(tank.TankNumber, 1000);
                Assert.AreEqual(true, inventories != null && inventories.Any());
                Assert.AreEqual(true, inventories.Count() >= 2, "Tank with tankNumber: " + tank.TankNumber + " does not has inventory records >=2");
            }
        }

        [TestMethod]
        public async Task Delivery_Manual_TestMethod1()
        {
            var tanks = atg.Tanks;
            var probeReading_OnStart = new ProbeReading()
            {
                Height = 1000,
                Water = 10,
                Temperature = new[] { (double)50 }
            };
            var probeReading_OnStop = new ProbeReading()
            {
                Height = 5000,
                Water = 30,
                Temperature = new[] { (double)55 }
            };
            Func<IProbeHandler, ProbeReading> mockReadingProducer_OnStart_Delivery = (ph) => probeReading_OnStart;
            Func<IProbeHandler, ProbeReading> mockReadingProducer_OnStop_Delivery = (ph) => probeReading_OnStop;

            probeHandlers.ForEach(ph => ph.Mock_SetProbeReadingValueProducer(mockReadingProducer_OnStart_Delivery));
            Assert.AreEqual(true, atg.Tanks != null);
            Assert.AreEqual(true, atg.Tanks.Count() == TankCount);

            await Task.Delay(atg.polling_fast_TankReadingTimer_Internval * 2);
            var volumeCaculatorTester = new HeightToVolumeCaculator(sharedTankProfileDatas);
            foreach (var tank in atg.Tanks)
            {
                var tankConfig = tankConfigs.First(tc => tc.TankNumber == tank.TankNumber);
                var tcCaculator = new TemperatureCompensationCaculator(tankConfig.ThermalCoefficient);

                var tankReading_startDelivery = await atg.StartOrStopManualDeliveryAsync(tank.TankNumber, "start");
                Assert.AreEqual(true, tankReading_startDelivery != null);
                Assert.AreEqual(true, tankReading_startDelivery.Height == probeReading_OnStart.Height,
                    $"for tankNumber: {tank.TankNumber}, expect: {probeReading_OnStart.Height}, but actual: {tankReading_startDelivery.Height}");
                Assert.AreEqual(true, tankReading_startDelivery.Water == probeReading_OnStart.Water);
                Assert.AreEqual(true, tankReading_startDelivery.Temperature.Value == probeReading_OnStart.Temperature.First());
            }

            probeHandlers.ForEach(ph => ph.Mock_SetProbeReadingValueProducer(mockReadingProducer_OnStop_Delivery));
            await Task.Delay(atg.polling_fast_TankReadingTimer_Internval * 2);
            foreach (var tank in atg.Tanks)
            {
                var tankReading_endDelivery = await atg.StartOrStopManualDeliveryAsync(tank.TankNumber, "stop");
                Assert.AreEqual(true, tankReading_endDelivery.Height == probeReading_OnStop.Height);
                Assert.AreEqual(true, tankReading_endDelivery.Water == probeReading_OnStop.Water);
                Assert.AreEqual(true, tankReading_endDelivery.Temperature.Value == probeReading_OnStop.Temperature.First());
            }

            foreach (var tank in atg.Tanks)
            {
                var tankDeliveries = await atg.GetTankDeliveryAsync(tank.TankNumber, 100);
                Assert.AreEqual(true, tankDeliveries != null && tankDeliveries.Any());
                var latestTankDelivery = tankDeliveries.First();
                Assert.AreEqual(true, latestTankDelivery.TankNumber == tank.TankNumber);
                // it just happened
                //Assert.AreEqual(true, DateTime.Now.Subtract(latestTankDelivery.StartingDateTime).TotalSeconds <= 2);
                //Assert.AreEqual(true, DateTime.Now.Subtract(latestTankDelivery.EndingDateTime).TotalSeconds <= InventorySamplingInterval * 4);
                //Assert.AreEqual(tankReading_startDelivery.TcVolume,
                //    tcCaculator.CaculateCompensatedVolume(tankOverallConfig.TcReference, tankReading_startDelivery.Volume.Value, tankReading_startDelivery.Temperature.Value));
                //Assert.AreEqual(true, tankReading_startDelivery.WaterVolume == volumeCaculatorTester.GetVolume(tankReading_startDelivery.Water.Value));
                //Assert.AreEqual(true, tankReading_startDelivery.Volume == volumeCaculatorTester.GetVolume(tankReading_startDelivery.Height.Value) - tankReading_startDelivery.WaterVolume);
                //Assert.AreEqual(tankReading_startDelivery.Ullage,
                //     volumeCaculatorTester.GetVolume(sharedTankProfileDatas.Max(p => p.Height)) - tankReading_startDelivery.Volume - tankReading_startDelivery.WaterVolume);

                await Task.Delay(atg.polling_fast_TankReadingTimer_Internval * 2);
            }
        }

        [TestMethod]
        public async Task Alarm_TestMethod1()
        {
            var receivedAlarms = new List<Edge.Core.IndustryStandardInterface.ATG.Alarm>();
            atg.OnAlarm += (s, alEvtArg) =>
            {
                receivedAlarms.AddRange(alEvtArg.Alarms);
            };
            var tanks = atg.Tanks;
            var probeReading_OnStart = new ProbeReading()
            {
                Height = 1000,
                Water = 10,
                Temperature = new[] { (double)50 }
            };
            var probeReading_OnStop = new ProbeReading()
            {
                Height = 5000,
                Water = 30,
                Temperature = new[] { (double)55 }
            };
            Func<IProbeHandler, ProbeReading> mockReadingProducer_OnStart_Delivery = (ph) => probeReading_OnStart;
            Func<IProbeHandler, ProbeReading> mockReadingProducer_OnStop_Delivery = (ph) => probeReading_OnStop;

            probeHandlers.ForEach(ph => ph.Mock_SetProbeReadingValueProducer(mockReadingProducer_OnStart_Delivery));
            Assert.AreEqual(true, atg.Tanks != null);
            Assert.AreEqual(true, atg.Tanks.Count() == TankCount);

            await Task.Delay(atg.polling_fast_TankReadingTimer_Internval * 2);
            var volumeCaculatorTester = new HeightToVolumeCaculator(sharedTankProfileDatas);
            foreach (var tank in atg.Tanks)
            {
                var tankConfig = tankConfigs.First(tc => tc.TankNumber == tank.TankNumber);
                var tcCaculator = new TemperatureCompensationCaculator(tankConfig.ThermalCoefficient);

                var tankReading_startDelivery = await atg.StartOrStopManualDeliveryAsync(tank.TankNumber, "start");
                Assert.AreEqual(true, tankReading_startDelivery != null);
                Assert.AreEqual(true, tankReading_startDelivery.Height == probeReading_OnStart.Height,
                    $"for tankNumber: {tank.TankNumber}, expect: {probeReading_OnStart.Height}, but actual: {tankReading_startDelivery.Height}");
                Assert.AreEqual(true, tankReading_startDelivery.Water == probeReading_OnStart.Water);
                Assert.AreEqual(true, tankReading_startDelivery.Temperature.Value == probeReading_OnStart.Temperature.First());
            }

            probeHandlers.ForEach(ph => ph.Mock_SetProbeReadingValueProducer(mockReadingProducer_OnStop_Delivery));
            await Task.Delay(atg.polling_fast_TankReadingTimer_Internval * 2);
            foreach (var tank in atg.Tanks)
            {
                var tankReading_endDelivery = await atg.StartOrStopManualDeliveryAsync(tank.TankNumber, "stop");
                Assert.AreEqual(true, tankReading_endDelivery.Height == probeReading_OnStop.Height);
                Assert.AreEqual(true, tankReading_endDelivery.Water == probeReading_OnStop.Water);
                Assert.AreEqual(true, tankReading_endDelivery.Temperature.Value == probeReading_OnStop.Temperature.First());
            }

            foreach (var tank in atg.Tanks)
            {
                var tankDeliveries = await atg.GetTankDeliveryAsync(tank.TankNumber, 100);
                Assert.AreEqual(true, tankDeliveries != null && tankDeliveries.Any());
                var latestTankDelivery = tankDeliveries.First();
                Assert.AreEqual(true, latestTankDelivery.TankNumber == tank.TankNumber);
                // it just happened
                //Assert.AreEqual(true, DateTime.Now.Subtract(latestTankDelivery.StartingDateTime).TotalSeconds <= 2);
                //Assert.AreEqual(true, DateTime.Now.Subtract(latestTankDelivery.EndingDateTime).TotalSeconds <= InventorySamplingInterval * 4);
                //Assert.AreEqual(tankReading_startDelivery.TcVolume,
                //    tcCaculator.CaculateCompensatedVolume(tankOverallConfig.TcReference, tankReading_startDelivery.Volume.Value, tankReading_startDelivery.Temperature.Value));
                //Assert.AreEqual(true, tankReading_startDelivery.WaterVolume == volumeCaculatorTester.GetVolume(tankReading_startDelivery.Water.Value));
                //Assert.AreEqual(true, tankReading_startDelivery.Volume == volumeCaculatorTester.GetVolume(tankReading_startDelivery.Height.Value) - tankReading_startDelivery.WaterVolume);
                //Assert.AreEqual(tankReading_startDelivery.Ullage,
                //     volumeCaculatorTester.GetVolume(sharedTankProfileDatas.Max(p => p.Height)) - tankReading_startDelivery.Volume - tankReading_startDelivery.WaterVolume);

                await Task.Delay(atg.polling_fast_TankReadingTimer_Internval * 2);
            }
        }
    }
}