using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading;
using Newtonsoft.Json;
using NLog;
using NLog.Config;
using ProfitGroup.Rdnl.Extensions;
using ProfitGroup.SampleService.Models;
using RnD.API;
using System.Management.Instrumentation;

namespace ProfitGroup.SampleService.Core.Services
{
    public class QueueHandler
    {
        private ILogger Logger
        {
            get
            {
                var loggerConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nlog.config");
                LogManager.Setup().LoadConfigurationFromFile(loggerConfig);
                LogManager.Configuration = new XmlLoggingConfiguration(loggerConfig);

                return LogManager.GetCurrentClassLogger();
            }
        }

        private readonly ConcurrentQueue<MT_InspLot> _queue;
        private readonly CancellationTokenSource _cancellationTokenSource;
        private readonly RDnLManager _manager;
        private readonly KsssInfoService _ksssInfoService;
        private readonly SampleService _sampleService;
        private readonly API _api;
        private readonly Appsettings _appSettings;

        public QueueHandler(RDnLManager manager,
            ConcurrentQueue<MT_InspLot> queue,
            API api)
        {
            _queue = queue;
            _cancellationTokenSource = new CancellationTokenSource();


            var loggerConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nlog.config");
            LogManager.Setup().LoadConfigurationFromFile(loggerConfig);
            LogManager.Configuration = new XmlLoggingConfiguration(loggerConfig);

            _manager = manager;
            _queue = queue;
            _ksssInfoService = new KsssInfoService(manager, api);
            _sampleService = new Core.Services.SampleService(manager, api, new SampleConfirmationService(manager, api));
            _api = api;

            var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json");
            _appSettings = JsonConvert.DeserializeObject<Appsettings>(File.ReadAllText(path));
        }

        public void StartProcessing()
        {
            Task.Run(async () =>
            {
                while (!_cancellationTokenSource.Token.IsCancellationRequested)
                {
                    MT_InspLot item;
                    while (_queue.TryDequeue(out item))
                    {
                        await Process(item);
                    }

                    Underload();
                }
            }, _cancellationTokenSource.Token);
        }

        public void StopProcessing()
        {
            _cancellationTokenSource.Cancel();
        }

        private async Task Process(MT_InspLot request)
        {
            if (request.Document == null)
            {
                return;
            }

            try
            {
                PushLogSeparator();
                #region 1. Проверка данных

                #region 1.1 Проверка на то, что пришли необходимые данные

                if (string.IsNullOrEmpty(request.Document.Plant))
                {
                    const string message = "В полученном пакете данных отсутствует параметр Plant (Площадка)";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                if (string.IsNullOrEmpty(request.Document.InspectonType))
                {
                    const string message = "В полученном пакете данных отсутствует параметр ControlType (Вид контроля)";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                if (string.IsNullOrEmpty(request.Document.Material))
                {
                    const string message =
                        "В полученном пакете данных отсутствует параметр ksss (Код KSSS), параметр Material";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                var customSampleCreateDto = new CustomSampleCreateDto
                {
                    PlantName = request.Document.Plant
                };

                #endregion

                #region 1.2 Проверки на верность полученных данных

                //var plant = await _ksssInfoService.GetPlantByName(request.Document.Plant);

                decimal plantId = 0;

                if (string.IsNullOrEmpty(request.Document.Plant) ||
                    decimal.TryParse(request.Document.Plant, out plantId) == false)
                {
                    var message =
                        $"Не удалось считать идентификатор площадки из полученного пакета данных, полученное значение: '{request.Document.Plant}'";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                var plant = await _ksssInfoService.GetPlantById(plantId);

                if (plant == null)
                {
                    var message =
                        $"Не удалось определить площадку с полученным идентификатором: '{request.Document.Plant}'";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                #endregion

                #endregion

                #region 2. Поиск инфополя, со значением, соответствующему полученному KSSS коду

                if (ulong.TryParse(request.Document.Material, out ulong materialId) == false)
                {
                    var message = $"Не удалось привести значение идентификатора в число: '{request.Document.Material}'";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                var ksssCodeInfoFields = await _ksssInfoService.GetInfoFieldsByKSSSCode(materialId.ToString());

                var specificationsIDs = ksssCodeInfoFields.Select(c => c.SP).Distinct().ToList();

                var activeSpecifications = await _ksssInfoService.GetActiveSpecificationsFromRange(specificationsIDs);

                if (activeSpecifications.Count == 0)
                {
                    var message = $"Отсутствует спецификация с кодом KSSS: '{request.Document.Material}'";
                    Logger.Error([{ request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                if (activeSpecifications.Count > 1)
                {
                    var message = $"Нашлось более одной спецификации с кодом KSSS: '{request.Document.Material}'";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                #endregion

                #region 3. Получение спецификации

                var specification = await _ksssInfoService.FindActiveSpecificationById(activeSpecifications[0].SP,
                    activeSpecifications[0].SP_VERSION);

                if (specification == null)
                {
                    var message =
                        $"Не удалось найти активную спецификацию по коду KSSS: {ksssCodeInfoFields[0].IIVALUE}";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                #endregion

                #region 4. Получение Product Code из спецификации

                customSampleCreateDto.ProductCode =
                    _ksssInfoService.GetProductCodeFromSpecificationKSSS(specification, out decimal? frameId);

                //ToDO: Проверить, что для данного продукт кода существует активная спецификация

                if (string.IsNullOrEmpty(customSampleCreateDto.ProductCode))
                {
                    var message = $"Не удалось определить код продукта у спецификации";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                #endregion

                #region 5. Маппинг вида контроля в формате OpcenterRdnL

                var mappedControl =
                    await _ksssInfoService.MapOperationControl(plant.LO, frameId, request.Document.InspectonType);

                if (mappedControl == null)
                {
                    var message =
                        $"Не удалось определить вид контроля: Локация: {plant.LO} / {plant.SHORT_DESC}, InspectonType: {request?.Document?.InspectonType}, FrameId: {frameId}";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                var controlType = await _ksssInfoService.GetControlTypeByCode(mappedControl.ControlTypeCode);

                if (controlType == null)
                {
                    var message =
                        $"В таблице видов контроля отсутствует вид с кодом: '{mappedControl.ControlTypeCode}'";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                customSampleCreateDto.OperationControlName = controlType.NAME_RU;

                #endregion

                #region 6. Определение упаковки (Вида тары), у объектов, которые не имеют стадии

                //  customSampleCreateDto.OperationControl != OperationControl.InputControl
                // Таблица: OpcenterRDnL.RndSuite.SiemtControlTypes
                // Если не входной контроль
                if (controlType.CODE != "IC" && string.IsNullOrEmpty(mappedControl.Stage))
                {
                    //Обработка частного случая, в котором определяется упаковка, в зависимости от объема партии
                    if (request.Document.InspectonType == "L010")
                    {
                        if (decimal.TryParse(request.Document.InspLotSize, out var lotSize))
                        {
                            if (request.Document.InspectonType == "L010")
                            {
                                customSampleCreateDto.Package =
                                    DetectPackageServiceByRangeValue.Detect("А/Ц", "В/Ц", 50_000, lotSize);
                            }
                        }
                        else
                        {
                            var message =
                                $@"Для вида контроля S4 ""{request.Document.InspectonType}"" не удалось определить упаковку, т.к. не удалось получить числовое значение InspLotSize, ожидалось число с плавающей точкой, фактическое значение: '{request?.Document?.InspLotSize}'";
                            Logger.Error($"[{request.GuigMsgOutb}]" + message);
                            throw new Exception(message);
                        }
                    }
                    else
                    {
                        customSampleCreateDto.Package =
                            _ksssInfoService.GetPackageNameFromSpecificationKSSS(specification);
                    }

                    if (_ksssInfoService.CheckAndMapPackageName(customSampleCreateDto.Package, plant.LO,
                            out string opCenterPackageName) || !string.IsNullOrEmpty(opCenterPackageName))
                    {
                        customSampleCreateDto.Package = opCenterPackageName;
                    }
                    else
                    {
                        throw new Exception(
                            $"Не удалось сопоставить наименование упаковки \"{customSampleCreateDto.Package}\" и локацией \"{plant.LO}\" ни с одной из упаковок в Opcenter!");
                    }

                    if (string.IsNullOrEmpty(customSampleCreateDto.Package) && specification.FrameId != 30)
                    {
                        var message = $"У инфокарты спецификации не заполнено инфополе: KD_Pack";
                        Logger.Error($"[{request.GuigMsgOutb}]" + message);
                        throw new Exception(message);
                    }
                }

                #endregion

                #region 7. Определение SampleType

                customSampleCreateDto.SampleType = _ksssInfoService.GetSampleType(
                    customSampleCreateDto.ProductCode,
                    customSampleCreateDto.OperationControlName,
                    customSampleCreateDto.Package,
                    mappedControl.Stage,
                    plant.LO
                );

                if (customSampleCreateDto.SampleType == null)
                {
                    var message =
                        $"Не удалось определить SampleType, ProductCode: {customSampleCreateDto.ProductCode}, OperationControlName: {customSampleCreateDto.OperationControlName}, Упаковка: \"{customSampleCreateDto.Package}\", Стадия: \"{mappedControl.Stage}\", возможно отсутствует ";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                var sampleType =
                    _manager.EventAwareObjectFactory.SampleType.GetFull(customSampleCreateDto.SampleType.ST,
                        customSampleCreateDto.SampleType.VERSION);

                if (sampleType.InfoProfiles.All(c => c.ShortDescription != "S4Integration_Probe"))
                {
                    var message =
                        $"SampleType в Opcenter настроен неверно, отсутствует инфокарта S4Integration_Probe, необходимая для интеграции";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                #endregion

                #region 8. Определение ключей создания пробы + заполнение

                var taskFilterDtos = _sampleService
                    .GetTaskKeys(plant.SHORT_DESC,
                        customSampleCreateDto.OperationControlName,
                        customSampleCreateDto.Package ?? mappedControl.Stage
                    )
                    .ToList();

                if (taskFilterDtos.Count == 0)
                {
                    var message =
                        $"Не было задано ни одного ключа создания объекта, либо не удалось определить контекстные ключи";
                    Logger.Error($"[{request.GuigMsgOutb}]" + message);
                    throw new Exception(message);
                }

                // Получаем только незаполненные параметры
                foreach (var taskFilter in taskFilterDtos.Where(taskFilter => string.IsNullOrEmpty(taskFilter.Value)))
                {
                    switch (taskFilter.Field)
                    {
                        case "SHORT_DESC@RndvSt":
                            taskFilter.Value = customSampleCreateDto.SampleType.SHORT_DESC;
                            break;
                        case "43@RndvStAu": // Упаковка
                            taskFilter.Value = customSampleCreateDto.Package.Replace(",", ".");
                            break;
                        case "38@RndvStAu": // Стадия
                            taskFilter.Value = mappedControl.Stage;
                            break;

                        default:
                            break;
                    }

                    //taskFilter.Value = taskFilter.Field switch
                    //{
                    //    // Краткое описание
                    //    "SHORT_DESC@RndvSt" => customSampleCreateDto.SampleType.SHORT_DESC,

                    //    // Упаковка
                    //    "43@RndvStAu" => customSampleCreateDto.Package.Replace(",", "."),

                    //    // Стадия
                    //    "38@RndvStAu" => mappedControl.Stage,

                    //    // Возвращаем значение
                    //    _ => taskFilter.Value
                    //};
                }

                #endregion

                #region 9. Создание пробы

                var sampleFullDto = _manager.EventAwareObjectFactory.Sample.Create(
                    customSampleCreateDto.SampleType.ST,
                    customSampleCreateDto.SampleType.VERSION,
                    taskFilterDtos);

                _manager.EventAwareObjectFactory.SaveChanges();

                #endregion

                #region 10. Заполнение инфокарт

                foreach (InfoCardSetting card in _appSettings.InfoCardSettings)
                {
                    var parameter = GetPropertyValue(request.Document, card.Name);

                    if (parameter != null)
                    {
                        card.Value = parameter.ToString();
                    }
                    else
                    {
                        Logger.Warn($"[{request.GuigMsgOutb}]" + 
                                    $"В полученном запросе отсутствует параметр: \"{card.Name}\". Локация: \"{card.Plant}\". Полученный запрос не имеет соответствующего поля/значения для записи");
                    }
                }

                _sampleService.FillSampleInfoCards(Convert.ToDecimal(sampleFullDto.ID), _appSettings.InfoCardSettings,
                    plant.DESCRIPTION);

                #endregion

                #region 11. Отправка Confirmation

                await _sampleService.SendConfirmation(request, sampleFullDto);

                #endregion

                #region 12. Возврат ответа на запрос

                var requestMessage = $"Запрос с Message Guid: {request.GuigMsgOutb} был успешно обработан";

                Logger.Log(LogLevel.Info, $"[{request.GuigMsgOutb}]" + requestMessage);

                #endregion

            }
            catch (Exception ex)
            {
                Logger.Error($"[{request.GuigMsgOutb}]" + ex.Message + "\n\n" + ex.StackTrace);

                await _sampleService.SendConfirmation(request, null, ex.Message);

            }
        }

        private void Underload()
        {
            Thread.Sleep(1000);
        }

        #region Приватные методы

        private void PushLogSeparator(int newLineCount = 0)
        {
            Logger.Info($"{new string('=', 100)}{new string('\n', newLineCount)}");
        }

        private object GetPropertyValue(object objectDocument, string propertyName)
        {
            Type type = objectDocument.GetType();
            PropertyInfo propertyInfo = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);

            if (propertyInfo != null)
                return propertyInfo.GetValue(objectDocument, null);
            else
                return null;
        }

        #endregion
    }
}