using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Controllers.V1.Base;
using PARR.API.Services.Interfaces;
using PARR.Common.Domain;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.Shortcodes.Models;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;

namespace PARR.API.Controllers.V1
{
    [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
    public class RobotTaskController : BaseApiController
    {
        private readonly IMapper mapper;
        private readonly SettingsFromDb settingsFromDb;
        private readonly IRobotConfigurationService robotConfigurationService;
        private readonly IEsppScheduleTransformService esppScheduleTransformService;
        private readonly ILogger<RobotTaskController> logger;
        private readonly IClientService clientService;
        private readonly IRobotHistoryService robotHistoryService;
        private readonly IShortcodesService shortcodesService;

        public RobotTaskController(
            IMapper mapper,
            SettingsFromDb settingsFromDb,
            IRobotConfigurationService robotConfigurationService,
            IEsppScheduleTransformService esppScheduleTransformService,
            ILogger<RobotTaskController> logger,
            IClientService clientService,
            IRobotHistoryService robotHistoryService,
            IShortcodesService shortcodesService
            )
        {
            this.mapper = mapper;
            this.settingsFromDb = settingsFromDb;
            this.robotConfigurationService = robotConfigurationService;
            this.esppScheduleTransformService = esppScheduleTransformService;
            this.logger = logger;
            this.clientService = clientService;
            this.robotHistoryService = robotHistoryService;
            this.shortcodesService = shortcodesService;
        }


        /// <summary>
        /// Получить задание для робота по коду робота и по статусу задания
        /// </summary>
        /// <param name="robotCode"></param>
        /// <param name="taskStatusCode"></param>
        /// <returns></returns>
        [HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
        public async Task<IActionResult> GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
        {
            //Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
            await robotConfigurationService.FindUnfulfilledTaskAndSetRobotErrorStatusAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);


            var query = robotConfigurationService.Get()
                                .Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);

            switch (robotCode)
            {
                case RobotsEnum.TemplateOrder:
                    // шаблоны
                    query = query
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.Unit)
                                .ThenInclude(t => t!.UnitValues)
                                    .ThenInclude(t => t.Field)
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.Unit)
                                .ThenInclude(t => t!.UnitValues)
                                    .ThenInclude(t => t.Value)
                        .Include(t => t.Template)
                            .ThenInclude(a => a!.Job)
                                .ThenInclude(t => t!.Group)
                                    .ThenInclude(g => g.GroupType)
                        .Include(t => t.Template)
                            .ThenInclude(w => w!.Job)
                            .ThenInclude(t => t!.Tnk)
                            .ThenInclude(s => s!.Subprocess)
                            .ThenInclude(p => p!.Process);

                    query = query
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.UnitsInTemplate);

                    break;

                case RobotsEnum.ScheduleOrder:
                    //расписание
                    query = query
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.Unit)
                                .ThenInclude(t => t!.UnitValues)
                                    .ThenInclude(t => t.Field)
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.Unit)
                                .ThenInclude(t => t!.UnitValues)
                                    .ThenInclude(t => t.Value)
                        .Include(t => t.Template)
                            .ThenInclude(a => a!.Job)
                                .ThenInclude(t => t!.Group)
                                    .ThenInclude(g => g.GroupType)
                        .Include(t => t.Template)
                            .ThenInclude(a => a!.Job)
                                .ThenInclude(t => t!.Group)
                                    .ThenInclude(t => t!.EsppSchValues)
                                        .ThenInclude(t => t!.EsppSchTypeConfig)
                                        .ThenInclude(t => t!.EsppSchTypeSchedule)
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.Job)
                                .ThenInclude(t => t!.Group)
                                    .ThenInclude(t => t!.ScheduleExcludeType)
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.Job)
                                .ThenInclude(t => t!.Group)
                                    .ThenInclude(t => t.ScheduleExcludeTypeCalendar)
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.Job)
                                .ThenInclude(t => t!.Tnk)
                        .Include(t => t.Template)
                            .ThenInclude(t => t!.UnitsInTemplate);

                    //выбираем только записи с созданными шаблонами (у которых статус 20 или 30), а только потом у них ищем расписания
                    var createdTemplates = robotConfigurationService.Get()
                                                        .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && (t.TaskStatusCode == (int)TaskStatusEnum.Ok))
                                                        .Select(t => t.TemplateId);
                    query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && createdTemplates.Contains(t.TemplateId));
                    //  query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && t.TemplateId==Guid.Parse("7cb7c3be-506d-40e0-a63f-4554edb52459"));
                    break;

                default:
                    break;
            }


            // сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
            query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);

            #region Так нельзя делать!!! Должно всегда сортироваться по NextRun и не важно какой тип у шаблона!!!
            // Сортируем сначала по StatusTypeId, т е те что used, будут первыми, потом сортируем по NextRun, чтобы ближайшие даты выполнились скорее
            //query = query.OrderBy(t => t.Template!.StatusTypeId).ThenBy(t => t.Template!.NextRun);
            #endregion

            RobotConfiguration? task = null;

            //ищем задание в ожидании, если нашли, выбираем его
            task = await query.FirstOrDefaultAsync(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait);

            if (task == null)
            {
                //ищем задания в работе, которые можно перезапустить
                //Поиск по `RobotStatusCode` = 22.
                //Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
                //и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.

                var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
                task = await query
                                .FirstOrDefaultAsync(t =>
                                                    t.RobotStatusCode == (int)RobotStatusEnum.InProgress
                                                    && t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
                                                    && t.LastRobotStatusUpdated < endDate
                                                );
            }

            if (task == null)
                return NotFound();

            if (requestQuery?.SetInProgressStatus == true)
            {
                var resultSetStatus = await SetInProgressStatusAsync(task.Id);
                if (resultSetStatus == false)
                {
                    logger.LogError($"Ошибка при установке статуса {RobotStatusEnum.InProgress.ToString()} для задания RobotConfigutationId {task.Id} (при выдаче задания роботу)");
                    return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
                }
            }

            switch (robotCode)
            {
                case RobotsEnum.TemplateOrder:
                    {    //RobotTaskTemplateResponse
                        var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(task);

                        //TODO Вынести в отдельный метод ShortcodesService

                        // Построим TemplateForShortcodes из уже загруженного task.Template
                        //var templateForShortcodes = new TemplateForShortcodes
                        //{
                        //    Id = task.Template!.Id,
                        //    Index = task.Template.Index,
                        //    JobId = task.Template.JobId,
                        //    UnitId = task.Template.UnitId,
                        //    Job = task.Template.Job == null ? null : new JobForShortcodes
                        //    {
                        //        Group = task.Template.Job.Group == null ? null : new JobGroupForShortcodes
                        //        {
                        //            Id = task.Template.Job.Group.Id,
                        //            GroupingUnitFieldId = task.Template.Job.Group.GroupingUnitFieldId,
                        //            GroupType = task.Template.Job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes
                        //            {
                        //                Code = task.Template.Job.Group.GroupType.Code
                        //            },
                        //            GroupName = task.Template.Job.Group.GroupName
                        //        },
                        //        Tnk = task.Template.Job.Tnk == null ? null : new TnkForShortcodes
                        //        {
                        //            Name = task.Template.Job.Tnk.Name,
                        //            ShortName = task.Template.Job.Tnk.ShortName ?? ""
                        //        },
                        //        WorkName = task.Template.Job.WorkName,
                        //        Name = task.Template.Job.Name
                        //    },
                        //    UnitsInTemplate = task.Template.UnitsInTemplate?.Select(uit => new UnitInTemplateForShortcodes { UnitId = uit.UnitId }).ToList() ?? new List<UnitInTemplateForShortcodes>()
                        //};

                        //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.FullDescription))
                        robotTaskTemplateResponse.FullDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!);
                        //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.ShortDescription))
                        robotTaskTemplateResponse.ShortDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ShortDescription, task.Template!);
                        //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.Solution))
                        robotTaskTemplateResponse.Solution = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.Solution, task.Template!);
                        //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.TnkName))
                        robotTaskTemplateResponse.TnkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.TnkName, task.Template!);
                        //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.WorkName))
                        robotTaskTemplateResponse.WorkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkName, task.Template!);
                        //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.WorkGroup))
                        robotTaskTemplateResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkGroup, task.Template!);
                        robotTaskTemplateResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ResponseArea, task.Template!);

                        return Ok(new Response<RobotTaskTemplateResponse>(robotTaskTemplateResponse, true));
                    }
                case RobotsEnum.ScheduleOrder:
                    { // если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
                        await UpdateLastNextRunDate(task);

                        //RobotTaskScheduleResponse
                        var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task);

                        //// Построим TemplateForShortcodes из уже загруженного task.Template
                        //var templateForShortcodes = new TemplateForShortcodes
                        //{
                        //    Id = task.Template!.Id,
                        //    Index = task.Template.Index,
                        //    JobId = task.Template.JobId,
                        //    UnitId = task.Template.UnitId,
                        //    Job = task.Template.Job == null ? null : new JobForShortcodes
                        //    {
                        //        Group = task.Template.Job.Group == null ? null : new JobGroupForShortcodes
                        //        {
                        //            GroupingUnitFieldId = task.Template.Job.Group.GroupingUnitFieldId,
                        //            GroupType = task.Template.Job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes
                        //            {
                        //                Code = task.Template.Job.Group.GroupType.Code
                        //            },
                        //            GroupName = task.Template.Job.Group.GroupName
                        //        },
                        //        Tnk = task.Template.Job.Tnk == null ? null : new TnkForShortcodes
                        //        {
                        //            Name = task.Template.Job.Tnk.Name,
                        //            ShortName = task.Template.Job.Tnk.ShortName
                        //        },
                        //        WorkName = task.Template.Job.WorkName,
                        //        Name = task.Template.Job.Name
                        //    },
                        //    UnitsInTemplate = task.Template.UnitsInTemplate?.Select(uit => new UnitInTemplateForShortcodes { UnitId = uit.UnitId }).ToList() ?? new List<UnitInTemplateForShortcodes>()
                        //};

                        //if (shortcodesService.IsAnyShortcodes(robotTaskScheduleResponse.WorkGroup))
                        robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
                        robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);

                        return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
                    }
                default:
                    break;
            }

            return BadRequest();
        }


        private async Task UpdateLastNextRunDate(RobotConfiguration task)
        {
            var template = task.Template!;

            #region old
            // передаем LastRun, если его нет, то NextRun
            //var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.LastRun ?? task.Template.ApplicationsInWork.NextRun);
            // всегда считаем по nextRun
            //var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template.NextRun);
            #endregion

            //пока у нас выключено автораспределение, считает по refDate
            //TODO: когда заработает автораспределение, будем думать!!!!!

            var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);

            if (nextRun != template.NextRun)
            {
                logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");

                //if (template.NextRun < DateTimeOffset.UtcNow)
                //{
                //    logger.LogDebug($"Для шаблона id {template.Id} обновляю lastRun, новое значение {template.NextRun}, старое значение {template.LastRun}");
                //    template.LastRun = template.NextRun;
                //}
                template.LastRun = template.NextRun;
                template.NextRun = nextRun;

                await robotConfigurationService.CommitAsync(new HistoryInitiator { InitiatorComment = "При получении задания роботом, обновил NextRun", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api });
            }

            #region Old, до выноса lastRun, nextRun в templates
            //var appInWorks = task.Template!.ApplicationsInWork!;

            //// передаем LastRun, если его нет, то NextRun
            ////var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.LastRun ?? task.Template.ApplicationsInWork.NextRun);
            //// всегда считаем по nextRun
            //var nextRun = await esppScheduleTransformService.GetNextDateAsync(task.Template!.ApplicationInWorkId, task.Template.ApplicationsInWork!.NextRun);

            //if (nextRun != appInWorks.NextRun)
            //{
            //    logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю nextRun, новое значение {nextRun}, старое значение {appInWorks.NextRun}");

            //    if (appInWorks.NextRun < DateTimeOffset.UtcNow)
            //    {
            //        logger.LogDebug($"Для appilcationInWork id {task.Template.ApplicationInWorkId}, шаблона id {task.TemplateId} обновляю lastRun, новое значение {appInWorks.NextRun}, старое значение {appInWorks.LastRun}");
            //        appInWorks.LastRun = appInWorks.NextRun;
            //    }

            //    appInWorks.NextRun = nextRun;

            //    await robotConfigurationService.CommitAsync();
            //}
            #endregion

        }


        /// <summary>
        /// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
        /// </summary>
        /// <param name="taskId"></param>
        /// <returns></returns>
        private async Task<bool> SetInProgressStatusAsync(Guid taskId)
        {
            var config = await robotConfigurationService.GetAsync(taskId);

            //изменение статуса робота
            robotConfigurationService.ChangeRobotStatus(RobotStatusEnum.InProgress, config!);

            if (!await robotConfigurationService.CommitAsync())
                return false;

            //записываем в лог робота
            var history = new RobotHistory
            {
                Id = Guid.NewGuid(),
                HistoryLevel = (int)RobotHistoryLevelEnum.Start,
                TaskStatusCode = config.TaskStatusCode,
                RobotConfigurationId = config.Id,
                RobotIp = clientService.GetClientIp()?.ToString()
            };

            if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
                return false;


            return true;
        }

    }
}
