using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json.Linq;
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories;
using UuidExtensions;

namespace DD.Persistence.Repository.Repositories
{
	public class TechMessagesRepository : ITechMessagesRepository
	{
        private readonly IDataSourceSystemRepository sourceSystemRepository;
		private DbContext db;

        public TechMessagesRepository(DbContext db, IDataSourceSystemRepository sourceSystemRepository)
        {
            this.db = db;
            this.sourceSystemRepository = sourceSystemRepository;
        }

        protected virtual IQueryable<TechMessage> GetQueryReadOnly() => db.Set<TechMessage>()
            .Include(e => e.System);

        public async Task<PaginationContainer<TechMessageDto>> GetPage(PaginationRequest request, CancellationToken token)
        {
            var query = GetQueryReadOnly();
            var count = await query.CountAsync(token);

            var sort = request.SortSettings != string.Empty
                ? request.SortSettings!
                : nameof(TechMessage.Timestamp);
            var entities = await query
                .SortBy(sort)
                .Skip(request.Skip)
                .Take(request.Take)
                .ToArrayAsync(token);

            var dto = new PaginationContainer<TechMessageDto>()
            {
                Skip = request.Skip,
                Take = request.Take,
                Count = count,
                Items = entities.Select(e => e.Adapt<TechMessageDto>())
            };

            return dto;
        }

		public async Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<Guid> systems, IEnumerable<int> categoryIds, CancellationToken token)
		{
			var query = GetQueryReadOnly();
			var result = await query
				.Where(e => !systems.Any() || systems.Contains(e.System.SystemId))
				.GroupBy(e => e.System.SystemId, (key, group) => new
				{
					System = group.FirstOrDefault()!.System.Name,
					Categories = group
						.Where(g => !categoryIds.Any() || categoryIds.Contains(g.CategoryId))
				})
				.ToArrayAsync(token);

            var entities = new List<MessagesStatisticDto>();
            foreach (var e in result)
            {
                var categories = e.Categories
                    .GroupBy(g => g.CategoryId)
                    .ToDictionary(c => c.Key, v => v.Count());
                var entity = new MessagesStatisticDto()
                {
                    System = e.System,
                    Categories = categories
                };
                entities.Add(entity);
            }

            return entities;
        }

		public async Task<int> AddRange(Guid systemId, IEnumerable<TechMessageDto> dtos, Guid userId, CancellationToken token)
		{
            await CreateSystemIfNotExist(systemId, token);

            var entities = new List<TechMessage>();
			foreach (var dto in dtos)
			{
				var entity = dto.Adapt<TechMessage>();

                await CreateSystemIfNotExist(systemId, token);

                entity.SystemId = systemId;

                entities.Add(entity);
            }

            await db.Set<TechMessage>().AddRangeAsync(entities, token);
            var result = await db.SaveChangesAsync(token);

            return result;
        }

        public async Task<IEnumerable<TechMessageDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
        {
            var query = GetQueryReadOnly();
            var entities = await query
                .Where(e => e.Timestamp >= dateBegin)
                .Take(take)
                .ToArrayAsync(token);
            var dtos = entities
                .Select(e => e.Adapt<TechMessageDto>());

            return dtos;
        }

		public async Task<IEnumerable<DataSourceSystemDto>> GetSystems(CancellationToken token)
		{
            var systems = await sourceSystemRepository.Get(token);

            return systems!;
		}

		public async Task<DatesRangeDto?> GetDatesRangeAsync(CancellationToken token)
		{
			var query = GetQueryReadOnly()
				.GroupBy(e => 1)
				.Select(group => new
				{
					Min = group.Min(e => e.Timestamp),
					Max = group.Max(e => e.Timestamp),
				});

			var values = await query.FirstOrDefaultAsync(token);
            if (values == null)
                return null;

			var result = new DatesRangeDto()
			{
				From = values?.Min ?? DateTimeOffset.MinValue,
				To = values?.Max ?? DateTimeOffset.MaxValue
			};

            return result;
        }

		private async Task CreateSystemIfNotExist(Guid systemId, CancellationToken token)
		{
			var systems = await sourceSystemRepository.Get(token);
			var system = systems?.FirstOrDefault(e => e.SystemId == systemId);

			if (system == null) 
			{
				system = new DataSourceSystemDto()
				{
					SystemId = systemId,
					Name = string.Empty
				};
                await sourceSystemRepository.Add(system, token);
            }
		}
	}
}