using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.Core.Common.Interfaces;
using PARR.Core.Common.Interfaces.RabbitServices;
using PARR.Core.Repositories.Interfaces;
using PARR.Domain.Common.Rabbit.Messages;
using PARR.Domain.Entities;
using PARR.Domain.Enums;
using PARR.EsppOrderManager.Services;
using PARR.EsppOrderManager.Settings;

namespace PARR.EsppOrderManager
{
    internal class EsppOrderManager : IEsppOrderManager
    {
        private readonly MqSettings mqSettings;
        private readonly IRabbitService mqService;
        private readonly ILogger<EsppOrderManager> logger;
        private readonly ITransformService transformService;
        private readonly IEsppOrderService esppOrderService;
        private readonly IServiceProvider serviceProvider;
        private readonly ResultTemplateSettings resultTemplateSettings;

        public EsppOrderManager(
            MqSettings mqSettings,
            IRabbitService mqService,
            ILogger<EsppOrderManager> logger,
            ITransformService transformService,
            IEsppOrderService esppOrderService,
            IServiceProvider serviceProvider,
            ResultTemplateSettings resultTemplateSettings
            )
        {
            this.mqSettings = mqSettings;
            this.mqService = mqService;
            this.logger = logger;
            this.transformService = transformService;
            this.esppOrderService = esppOrderService;
            this.serviceProvider = serviceProvider;
            this.resultTemplateSettings = resultTemplateSettings;
        }

        public async Task StartAsync()
        {
            // логика работы описана в документации

            var isConnected = await mqService.InitConsumerAsync(mqSettings, ManageOrderAsync);

            if (!isConnected)
                throw new Exception("Ошибка при подключении к RabbitMq");

            //test, and remove async in method name
            //await ManageOrderAsync("{\"OrderId\":\"49191af5-c313-4388-9720-a61fd13f15a8\"}");
        }

        public async Task StopAsync()
        {
           await mqService.DisposeAsync();
        }


        private async Task ManageOrderAsync(string msg)
        {
            //logger.LogDebug($"Получили сообщение: {msg}");

            var objFromQuery = transformService.GetModelFromJson<OrderManageMq>(msg);
            if (objFromQuery == null)
                return;

            using (var scope = serviceProvider.CreateScope())
            {
                var orderService = scope.ServiceProvider.GetService<IOrderRepository>();
                if (orderService == null)
                    throw new Exception("Не смог получить серивс IOrderService, scope.ServiceProvider.GetService<IOrderService>()");

                await ManageOrderToScopeAsync(orderService, objFromQuery);
            }

            logger.LogDebug($"Закончил работу с сообщением: {msg}");
        }


        private async Task ManageOrderToScopeAsync(IOrderRepository orderService, OrderManageMq objFromQuery)
        {
            var order = await orderService.Get()
                              .Include(t => t.AgentHistories).ThenInclude(t => t.AgentHistoryLevel)
                              .Include(t => t.OrderStatus)
                              .Include(t => t.NextStatus)
                              .Include(t => t.Template).ThenInclude(t => t!.Job).ThenInclude(t => t!.Tnk)
                              .FirstOrDefaultAsync(t => t.Id == objFromQuery.OrderId);

            if (!IsValidOrder(ref order, ref objFromQuery))
                return;

            logger.LogDebug($"Найден наряд в БД: {order!.Number}");

            var resultMsg = GenerateResultMsg(order!);
            var workDuration = CalcWorkDuration(order!);

            // Изменям статус наряда в ЕСПП
            var result = await esppOrderService.ChangeOrderToEsppAsync(order!, resultMsg, workDuration);

            if (result)
            {
                logger.LogInformation($"Статусы наряда {order!.Number} в ЕСПП обновлены. Установлен статус: {order.NextStatus!.Description}");

                //обновляем статусы в БД
                order.StatusCode = order.NextStatusCode!.Value;
                order.DateModified = DateTimeOffset.UtcNow;

                if (!await orderService.CommitAsync())
                {
                    logger.LogError($"Ошибка при обновлении статуса в БД у наряда: {order.Number}. Не установлен статус {order.NextStatus!.Description}");
                }

            }
            else
                logger.LogError($"Ошибка при обновлении статуса наряда {order!.Number}. Статус не изменен.");
        }


        private bool IsValidOrder(ref Order? order, ref OrderManageMq query)
        {
            if (order == null)
            {
                logger.LogError($"Не найден наряд с OrderId: {query.OrderId}");
                return false;
            }

            if (order.StatusCode == order.NextStatusCode)
            {
                logger.LogInformation($"У наряда {order.Id}, {order.Number} статусы равны, ничего не делаем. Статус: {order.OrderStatus!.Description}");
                return false;
            }

            if (!order.NextStatusCode.HasValue)
            {
                logger.LogWarning($"У наряда {order.Id}, {order.Number} не установлено поле {nameof(order.NextStatusCode)}");
                return false;
            }

            return true;
        }


        /// <summary>
        /// Генерирует текст поля Решение, в зависимости от статуса наряда и от истории Агента
        /// </summary>
        /// <param name="order"></param>
        /// <returns></returns>
        private string GenerateResultMsg(Order order)
        {
            var agentHistory = order.AgentHistories
                                        .OrderBy(t => t.DateCreated)
                                        .Select(t => $"{t.DateCreated} :: {t.AgentHistoryLevel?.Name} :: {t.Message}");

            //"Template": "{base}\n{msgAgent}",
            //"MsgAgent": "Работы выполнены средствами автоматизации.\nЖурнал работы:\n{agentHistory}",
            //"MsgAgentEmpty": "Работы должны были выполняться средствами автоматизации. От средств автоматизации не поступило сообщений. Работы не выполнены."

            //Шаблон поля Решение
            var template = resultTemplateSettings.Template;

            //Поле решение из ApplicationsInWork
            var baseMsg = order.Template!.Job!.Group!.Solution;

            //Формируем сообщения от Агента
            var msgAgent = agentHistory.Any()
                                            ? resultTemplateSettings.MsgAgent.Replace("{agentHistory}", string.Join("\n", agentHistory))
                                            : resultTemplateSettings.MsgAgentEmpty;

            return template.Replace("{base}", baseMsg).Replace("{msgAgent}", msgAgent);
        }


        /// <summary>
        /// Рассчиать трудозатраты
        /// </summary>
        /// <param name="order"></param>
        /// <returns></returns>
        private TimeSpan CalcWorkDuration(Order order)
        {
            var agentHistory = order.AgentHistories
                                        .OrderBy(t => t.DateCreated);

            if (!agentHistory.Any())
                return GetTnkDurationByScriptDuration(TimeSpan.Zero);

            var dateStart = agentHistory.LastOrDefault(t => t.HistoryLevelId == (int)AgentHistoryLevelEnum.Start)?.DateCreated;
            var dateEnd = agentHistory.LastOrDefault(t => t.HistoryLevelId == (int)AgentHistoryLevelEnum.End)?.DateCreated;

            if (dateStart.HasValue && dateEnd.HasValue)
            {
                var duration = dateEnd.Value - dateStart.Value;
                logger.LogDebug($"Продолжительность работы скрипта: {duration}");

                return GetTnkDurationByScriptDuration(duration);
            }

            return GetTnkDurationByScriptDuration(TimeSpan.Zero);
        }


        /// <summary>
        /// Получить время для списания ТНК по времени работы скрипта
        /// </summary>
        /// <param name="scriptDuration"></param>
        /// <returns></returns>
        private TimeSpan GetTnkDurationByScriptDuration(TimeSpan scriptDuration)
        {
            // минимальное значение 1 минута
            var minDuration = new TimeSpan(0, 1, 0);

            if (scriptDuration < minDuration)
            {
                logger.LogDebug($"Продолжительность меньше минимального, беру минимальное: {minDuration}. {scriptDuration}<{minDuration}");

                return minDuration;
            }
            else
                return scriptDuration;
        }

    }
}
