using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Extensions;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.RobotRepositories;
using PARR.Domain.DTOs.RobotMetrics;
using PARR.Domain.Enums;
using PARR.Domain.Exceptions;

namespace PARR.Core.Services.RobotMetrics
{
    internal class RobotMetricsService : IRobotMetricsService
    {
        private readonly IRobotConfigurationSnapshotRepository _snapshotRepository;
        private readonly IRobotConfigurationRepository _configurationRepository;
        private readonly ILogger<RobotMetricsService> _logger;

        public RobotMetricsService(
            IRobotConfigurationSnapshotRepository snapshotRepository,
            IRobotConfigurationRepository configurationRepository,
            ILogger<RobotMetricsService> logger
            )
        {
            _snapshotRepository = snapshotRepository;
            _configurationRepository = configurationRepository;
            _logger = logger;
        }


        public async Task<List<FilteredChartPoint>> GetFilteredMetricsAsync(MetricFilter filter, CancellationToken cancellationToken)
        {
            // ------- Правильность расчетов этого метода доконца не проверена ------- 

            if (filter == null)
                throw new AppValidationException("Фильтр не может быть пустым.");

            if (filter.IntervalMinutes < 1)
                throw new AppValidationException("Интервал группировки не может быть меньше 1 минуты.");

            var query = _snapshotRepository.Get().AsNoTracking();

            if (filter.RobotCode.HasValue)
                query = query.Where(t => t.RobotCode == (int)filter.RobotCode.Value);

            if (filter.RobotStatusCode.HasValue)
                query = query.Where(t => t.RobotStatusCode == (int)filter.RobotStatusCode.Value);

            if (filter.TaskStatusCode.HasValue)
                query = query.Where(t => t.TaskStatusCode == (int)filter.TaskStatusCode);

            // Если даты не переданы, берем последние 24 часа по умолчанию
            var dateFrom = filter.DateFrom ?? DateTimeOffset.UtcNow.AddDays(-1);
            query = query.Where(s => s.DateCreated >= dateFrom);

            if (filter.DateTo.HasValue)
                query = query.Where(s => s.DateCreated <= filter.DateTo.Value);

            var dbData = await query.ToListAsync(cancellationToken);

            var dbGrouped = dbData
                                .GroupBy(s => RoundToInterval(s.DateCreated, filter.IntervalMinutes))
                                .ToDictionary(t => t.Key, t => t.ToList());

            var dateTo = filter.DateTo ?? DateTimeOffset.UtcNow;

            var result = GenerateTimeGrid(dateFrom, dateTo, filter.IntervalMinutes)
                            .Select(time => new FilteredChartPoint(
                                    Timestamp: time,
                                    //Count: dbGrouped.TryGetValue(time, out var points) ? points.Max(x => x.Count) : 0
                                    Count: dbGrouped.TryGetValue(time, out var points)
                                                ? points.GroupBy(x => x.DateCreated) // Группируем по точной минуте снапшота
                                                        .Select(g => g.Sum(x => x.Count)) // Складываем всё, что подошли под фильтр в эту минуту
                                                        .MaxOrDefault() // Берем максимальный пик за весь интервал (например, за час)
                                                : 0 // Если снапшотов не было — честный ноль
                            )).ToList();

            return result;
        }


        public async Task<List<RobotStatusChartPoint>> GetRobotStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken)
        {
            ValidateRobot(robotCode);
            CalculatePeriodDates(period, out var fromDate, out var intervalMinutes);

            // История из снапшотов
            var snapshots = await _snapshotRepository.Get()
                                        .AsNoTracking()
                                        .Where(s => s.RobotCode == (int)robotCode && s.DateCreated >= fromDate)
                                        .ToListAsync(cancellationToken);

            var dbGrouped = snapshots
                                .GroupBy(t => RoundToInterval(t.DateCreated, intervalMinutes))
                                .ToDictionary(t => t.Key, t => t.ToList());

            // Генерим сетку значений, если значений нет, вставляем нули
            var history = GenerateTimeGrid(fromDate, DateTimeOffset.UtcNow, intervalMinutes)
                                .Select(time => dbGrouped.TryGetValue(time, out var points)
                                    ? new RobotStatusChartPoint(
                                            Timestamp: time,
                                            // группируем по точной минуте снапшота, складываем внутренности, а потом ищем пик (Max) за весь интервал
                                            Wait: //points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Wait).MaxOrDefault(x => x.Count),
                                                points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Wait)
                                                        .GroupBy(x => x.DateCreated)
                                                        .Select(t => t.Sum(x => x.Count))
                                                        .MaxOrDefault(),
                                            InProgress: //points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.InProgress).MaxOrDefault(x => x.Count),
                                                points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.InProgress)
                                                        .GroupBy(x => x.DateCreated)
                                                        .Select(g => g.Sum(x => x.Count))
                                                        .MaxOrDefault(),
                                            Error: //points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Error).MaxOrDefault(x => x.Count)
                                                points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Error)
                                                        .GroupBy(x => x.DateCreated)
                                                        .Select(g => g.Sum(x => x.Count)) // Честная сумма всех ошибок в рамках одной минуты снапшота
                                                        .MaxOrDefault(),
                                            Complete:
                                                points.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Complete)
                                                        .GroupBy(x => x.DateCreated)
                                                        .Select(g => g.Sum(x => x.Count)) // Честная сумма всех ошибок в рамках одной минуты снапшота
                                                        .MaxOrDefault()
                                        )
                                    : new RobotStatusChartPoint(time, Wait: 0, InProgress: 0, Error: 0, Complete: 0)
                                ).ToList();

            // Последнее значение в конце графика из реальной таблицы
            var liveRaw = await _configurationRepository
                                    .Get()
                                    .AsNoTracking()
                                    .Where(t => t.RobotCode == (int)robotCode)
                                    .GroupBy(t => t.RobotStatusCode)
                                    .Select(g => new { RobotStatusCode = g.Key, Count = g.Count() })
                                    .ToListAsync(cancellationToken);

            history.Add(new RobotStatusChartPoint(
                 Timestamp: DateTimeOffset.UtcNow,
                 Wait: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Wait).Sum(x => x.Count),
                 InProgress: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.InProgress).Sum(x => x.Count),
                 Error: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Error).Sum(x => x.Count),
                 Complete: liveRaw.Where(x => x.RobotStatusCode == (int)RobotStatusEnum.Complete).Sum(x => x.Count)
            ));

            return history;
        }


        public async Task<List<TaskStatusChartPoint>> GetTaskStatusMetricsAsync(RobotsEnum robotCode, ChartPeriod period, CancellationToken cancellationToken)
        {
            ValidateRobot(robotCode);
            CalculatePeriodDates(period, out var fromDate, out var intervalMinutes);

            // История из снапшотов
            var snapshots = await _snapshotRepository.Get()
                                        .AsNoTracking()
                                        .Where(s => s.RobotCode == (int)robotCode && s.DateCreated >= fromDate)
                                        .ToListAsync(cancellationToken);

            var dbGrouped = snapshots
                                .GroupBy(s => RoundToInterval(s.DateCreated, intervalMinutes))
                                .ToDictionary(t => t.Key, t => t.ToList());

            var history = GenerateTimeGrid(fromDate, DateTimeOffset.UtcNow, intervalMinutes)
                                .Select(time => dbGrouped.TryGetValue(time, out var points)
                                    ? new TaskStatusChartPoint(
                                            Timestamp: time,
                                            // группируем по точной минуте снапшота, складываем внутренности, а потом ищем пик (Max) за весь интервал
                                            Creating: //points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Creating).MaxOrDefault(x => x.Count),
                                                points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Creating)
                                                    .GroupBy(x => x.DateCreated)
                                                    .Select(g => g.Sum(x => x.Count))
                                                    .MaxOrDefault(),
                                            Updating: //points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Updating).MaxOrDefault(x => x.Count),
                                                points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Updating)
                                                    .GroupBy(x => x.DateCreated)
                                                    .Select(g => g.Sum(x => x.Count))
                                                    .MaxOrDefault(),
                                            Ok: //points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Ok).MaxOrDefault(x => x.Count)
                                                points.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Ok)
                                                    .GroupBy(x => x.DateCreated)
                                                    .Select(g => g.Sum(x => x.Count))
                                                    .MaxOrDefault()
                                        )
                                    : new TaskStatusChartPoint(time, Creating: 0, Updating: 0, Ok: 0)
                                ).ToList();

            // Живой текущий кадр в конец графика
            var liveRaw = await _configurationRepository
                                    .Get()
                                    .AsNoTracking()
                                    .Where(t => t.RobotCode == (int)robotCode)
                                    .GroupBy(t => t.TaskStatusCode)
                                    .Select(g => new { TaskStatusCode = g.Key, Count = g.Count() })
                                    .ToListAsync(cancellationToken);

            history.Add(new TaskStatusChartPoint(
                                        Timestamp: DateTimeOffset.UtcNow,
                                        Creating: liveRaw.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Creating).Sum(x => x.Count),
                                        Updating: liveRaw.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Updating).Sum(x => x.Count),
                                        Ok: liveRaw.Where(x => x.TaskStatusCode == (int)TaskStatusEnum.Ok).Sum(x => x.Count)
                                    ));

            return history;
        }


        private void ValidateRobot(RobotsEnum robotCode)
        {
            if (!Enum.IsDefined(typeof(RobotsEnum), robotCode))
                throw new NotFoundException($"Робот с кодом {robotCode} не найден в системе.");
        }


        private void CalculatePeriodDates(ChartPeriod period, out DateTimeOffset fromDate, out int intervalMinutes)
        {
            switch (period)
            {
                case ChartPeriod.TwoHours:
                    fromDate = DateTimeOffset.UtcNow.AddHours(-2);
                    intervalMinutes = 2;
                    break;
                case ChartPeriod.TwentyFourHours:
                    fromDate = DateTimeOffset.UtcNow.AddDays(-1);
                    intervalMinutes = 30;
                    break;
                case ChartPeriod.SevenDays:
                    fromDate = DateTimeOffset.UtcNow.AddDays(-7);
                    intervalMinutes = 60;
                    break;
                default:
                    throw new AppValidationException("Указан неподдерживаемый период времени.");
            }
        }


        private DateTimeOffset RoundToInterval(DateTimeOffset dt, int intervalMinutes)
        {
            var minutes = (dt.Minute / intervalMinutes) * intervalMinutes;
            return new DateTimeOffset(dt.Year, dt.Month, dt.Day, dt.Hour, minutes, 0, dt.Offset);
        }

        /// <summary>
        /// Генератор сетки времени
        /// </summary>
        /// <param name="fromDate"></param>
        /// <param name="toDate"></param>
        /// <param name="intervalMinutes"></param>
        /// <returns></returns>
        private IEnumerable<DateTimeOffset> GenerateTimeGrid(DateTimeOffset fromDate, DateTimeOffset toDate, int intervalMinutes)
        {
            var startTime = RoundToInterval(fromDate, intervalMinutes);
            var endTime = RoundToInterval(toDate, intervalMinutes);

            for (var time = startTime; time <= endTime; time = time.AddMinutes(intervalMinutes))
            {
                yield return time;
            }
        }

    }
}
