using AutoMapper;
using FluentValidation;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Writes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Contracts.V1.Responses.Statistics;
using PARR.API.Controllers.V1.Base;
using PARR.API.Services.Interfaces;
using PARR.API.Settings;
using PARR.Core.Common.Interfaces;
using PARR.Core.Repositories.Interfaces;
using PARR.Domain.Common.Roles;
using PARR.Domain.Enums;
using PARR.Domain.Settings;

namespace PARR.API.Controllers.V1.Statistics
{
    /// <summary>
    /// Статистика доступности роботов
    /// </summary>
    [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
    public class StatRobotStateController : BaseApiController
    {
        private readonly IInfluxDbService influxDbService;
        //private readonly IValidator<RobotStateRequest> validator;
        private readonly InfluxDbSettings influxDbSettings;
        private readonly MonitoringSettings monitoringSettings;
        private readonly IClientService clientService;
        private readonly IUserRepository userService;
        private readonly IMapper mapper;

        public StatRobotStateController(
            IInfluxDbService influxDbService,
            //IValidator<RobotStateRequest> validator,
            InfluxDbSettings influxDbSettings,
            MonitoringSettings monitoringSettings,
            IClientService clientService,
            IUserRepository userService,
            IMapper mapper
            )
        {
            this.influxDbService = influxDbService;
            //this.validator = validator;
            this.influxDbSettings = influxDbSettings;
            this.monitoringSettings = monitoringSettings;
            this.clientService = clientService;
            this.userService = userService;
            this.mapper = mapper;
        }


        /// <summary>
        /// Доступность робота
        /// </summary>
        /// <returns></returns>
        [HttpGet(ApiRoutes.StatRobotState.Get)]
        public async Task<IActionResult> Get([FromRoute] RobotsAllEnum robot, [FromQuery] RobotStateGetQuery query)
        {
            var startAt = string.IsNullOrEmpty(query.StartAt) ? "-2h" : query.StartAt;

            var robotSettings = monitoringSettings.Robots.FirstOrDefault(t => t.Name == robot.ToString());
            if (robotSettings == null)
                return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = $"Ошибка при получении статистики. Неверно указаны настройки мониторинга." } }));

            // var inaccessibilityTime = TimeSpan.Parse("00:03:00");
            var inaccessibilityTime = robotSettings.InaccessibilityTime;


            var result = await influxDbService.QueryAsync(async handler =>
             {
                 //var flux = "from(bucket:\"robots\") " +
                 //          "|> range(start: 0) " +
                 //          "|> filter(fn: (r) => " +
                 //          "r._measurement == \"altitude\" and " +
                 //          "r._value > 3500)";

                 var query = $"from(bucket:\"{influxDbSettings.Robots!.Bucket}\") " +
                                 //$"|> range(start: -3h) " +
                                 $"|> range(start: {startAt}) " +
                                 $"|> filter(fn: (r) => " +
                                    $"r._measurement == \"robots\" " +
                                    $"and r.robot == \"{robot.ToString()}\"" +
                                 $")";


                 var tables = await handler.QueryAsync(query, influxDbSettings.Organization);

                 return tables.SelectMany(table =>
                     table.Records.Select(record => new InfuxRowData
                     {
                         Date = (DateTimeOffset)record.GetTimeInDateTime(),
                         IsUp = int.Parse(record.GetValue()?.ToString() ?? "0"),
                         Ip = record.GetValueByKey("ip")?.ToString()
                     })
                 );
             }, influxDbSettings.Robots!.Token);

            if (result == null)
                return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = $"Ошибка при получении статистики" } }));

            var groupingByRobotIp = result.GroupBy(t => t.Ip);

            var response = new List<StatRobotStateResponse>();

            foreach (var robotItem in groupingByRobotIp)
            {
                var user = await userService.Get().FirstOrDefaultAsync(t => t.Ip == robotItem.Key);

                response.Add(new StatRobotStateResponse
                {
                    RobotIp = robotItem.Key,
                    User = mapper.Map<UserBaseResponse>(user),
                    Data = CalcInaccessTime(robotItem.Select(t => new StatRobotStateData { Date = t.Date, IsUp = t.IsUp }).ToList(), inaccessibilityTime)
                });
            }

            //var listWithInaccessTime = CalcInaccessTime(result.ToList(), inaccessibilityTime);


            return Ok(new Response<List<StatRobotStateResponse>>(response.OrderBy(t => t.User?.Name).ToList(), true));
        }


        /// <summary>
        /// Статистика доступности роботов. Передать инф-у о доступности
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPost(ApiRoutes.StatRobotState.Send)]
        public async Task<IActionResult> Send([FromBody] RobotStateRequest request)
        {
            //var resultValidate = await validator.ValidateAsync(request);
            //if (!resultValidate.IsValid)
            //    return BadRequest(new Response(resultValidate.Errors));

            var result = influxDbService.Write(write =>
                {
                    var point = PointData.Measurement("robots")
                                         .Tag("robot", request.Robot.ToString())
                                         .Tag("ip", clientService.GetClientIp()?.ToString())
                                         //.Field("robot", (int)request.Robot)
                                         .Field("isUp", 1)
                                         .Timestamp(DateTime.UtcNow, WritePrecision.Ns);

                    write.WritePoint(point, bucket: influxDbSettings.Robots!.Bucket, org: influxDbSettings.Organization);
                }, influxDbSettings.Robots!.Token);

            if (!result)
                return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = $"Ошибка при отправке данных" } }));

            return Ok(new Response<string>("", true));
        }


        /// <summary>
        /// Рассчитывает время когда робот был недоступен
        /// </summary>
        /// <param name="data"></param>
        /// <param name="inaccessibilityTime"></param>
        /// <returns></returns>
        private List<StatRobotStateData> CalcInaccessTime(List<StatRobotStateData> data, TimeSpan inaccessibilityTime)
        {

            var inaccessTimeList = new List<StatRobotStateData>();

            DateTimeOffset? lastTime = null;
            foreach (var item in data.OrderBy(t => t.Date))
            {
                if (!lastTime.HasValue)
                {
                    lastTime = item.Date;
                    continue;
                }

                lastTime = lastTime.Value.Add(inaccessibilityTime);
                while (lastTime < item.Date)
                {
                    //есть время когда робот был недоступен, добаляем в список недоступности
                    inaccessTimeList.Add(new StatRobotStateData { Date = lastTime.Value, IsUp = 0 });
                    lastTime = lastTime.Value.Add(inaccessibilityTime);
                }

                // робот был доступен, все ок
                lastTime = item.Date;
            }


            //if (lastTime == null)
            //{
            //    // не было вообще никакой статистики, считаем что он недоступен
            //    inaccessTimeList.Add(new StatRobotStateResponse { Date = DateTimeOffset.UtcNow, IsUp = 0 });
            //}

            // Проверяем, вдруг с последней даты активности до сейчас прошло уже много времени и он не доступен
            if (lastTime.HasValue)
            {
                lastTime = lastTime.Value.Add(inaccessibilityTime);
                while (lastTime.Value < DateTimeOffset.UtcNow)
                {
                    inaccessTimeList.Add(new StatRobotStateData { Date = lastTime.Value, IsUp = 0 });
                    lastTime = lastTime.Value.Add(inaccessibilityTime);
                }
            }



            data.AddRange(inaccessTimeList);

            return data.OrderBy(t => t.Date).ToList();
        }
    }


    class InfuxRowData
    {
        public DateTimeOffset Date { get; set; }

        public int IsUp { get; set; }

        public string? Ip { get; set; }
    }
}
