using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using DocumentFormat.OpenXml.Bibliography;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;


namespace AsbCloudInfrastructure.Services.SAUB;

public class MessageRepository : IMessageRepository
{
    private readonly IAsbCloudDbContext db;
    private readonly IMemoryCache memoryCache;
    private readonly ITelemetryService telemetryService;

    public MessageRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService)
    {
        this.db = db;
        this.memoryCache = memoryCache;
        this.telemetryService = telemetryService;
    }

    public async Task<IEnumerable<TelemetryMessageDto>> GetMessagesAsync(MessageTelemetryRequest request, CancellationToken token)
    {
        var query = BuildQuery(request, token);

        var entities = await query.ToArrayAsync(token);

        var dtos = entities.Select(m => m.Adapt<TelemetryMessageDto>());

        return dtos;      
    }

    public async Task<PaginationContainer<MessageDto>> GetPaginatedMessagesAsync(MessageTelemetryRequest request, CancellationToken token)
    {
        var result = new PaginationContainer<MessageDto>
        {
            Skip = request.Skip ?? 0,
            Take = request.Take ?? 32,
        };

        if (request.Telemetries.IsNullOrEmpty() || request.Events.IsNullOrEmpty())
            return result;

        var query = BuildQuery(request, token);

        if (request.SortFields?.Any() == true)
        {
            query = query.SortBy(request.SortFields);
        }

        result.Count = query.Count();

        var messagesList = await query
            .Skip(result.Skip)
            .Take(result.Take)
            .AsNoTracking()
            .ToArrayAsync(token);

        if (messagesList.Count() == 0)
            return result;

        var allUsers = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryUser>(), token);
        var users = allUsers.Where(u => request.Telemetries!.Select(t => t.Id).Contains(u.IdTelemetry));

        if (!request.Events.Any())
            return result;

        var eventsDict = request.Events.ToDictionary(x => x.Id);
        var usersDict = users.ToDictionary(x => x.IdUser, x => x);

        var messagesDtoList = new List<MessageDto>();

        foreach (var message in messagesList)
        {
            var messageDto = new MessageDto
            {
                Id = message.Id,
                WellDepth = message.WellDepth,
            };

            var telemetry = request.Telemetries.Where(t => t.Id == message.IdTelemetry).FirstOrDefault();

            if(telemetry != null && telemetry.TimeZone != null)
            messageDto.DateTime = message.DateTime.ToOffset(TimeSpan.FromHours(telemetry.TimeZone.Hours));

            if (message.IdTelemetryUser is not null)
            {
                if (usersDict.TryGetValue((int)message.IdTelemetryUser, out TelemetryUser? user))
                {
                    messageDto.User = user.MakeDisplayName();
                }
                else
                    messageDto.User = message.IdTelemetryUser.ToString();
            }

            if (eventsDict.TryGetValue(message.IdEvent, out TelemetryEventDto? e))
            {
                messageDto.CategoryId = e.IdCategory;
                messageDto.Message = e.MakeMessageText([
                    message.Arg0,
                    message.Arg1,
                    message.Arg2,
                    message.Arg3
                ]);
            }
            messagesDtoList.Add(messageDto);
        }

        result.Items = result.Items.Concat(messagesDtoList);

        return result;
    }
    public IQueryable<TelemetryMessage> BuildQuery(MessageTelemetryRequest request, CancellationToken token)
    {
        var idsTelemetries = request.Telemetries.Select(t => t.Id);
        var eventIds = request.Events.Select(e => e.Id);

        var query = db.TelemetryMessages
            .Where(m => idsTelemetries.Contains(m.IdTelemetry))
            .Where(m => eventIds.Contains(m.IdEvent));

        if (request.Begin is not null)
        {
            var beginUtc = request.Begin.Value.ToUniversalTime();
            query = query.Where(m => m.DateTime >= beginUtc);
        }

        if (request.End is not null)
        {
            var endUtc = request.End.Value.ToUniversalTime();
            query = query.Where(m => m.DateTime <= endUtc);
        }

        query = query.OrderByDescending(m => m.DateTime);

        return query;
    }

    public Task InsertAsync(string uid, IEnumerable<TelemetryMessageDto> dtos,
    CancellationToken token = default)
    {
        if (!dtos.Any())
            return Task.CompletedTask;

        var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid);

        foreach (var dto in dtos)
        {
            var entity = dto.Adapt<TelemetryMessage>();
            entity.Id = 0;
            entity.IdTelemetry = telemetry.Id;
            entity.DateTime = dto.Date.ToUniversalTime();
            db.TelemetryMessages.Add(entity);
        }

        return db.SaveChangesAsync(token);
    }

    private IQueryable<TelemetryMessage> BuildQuery(TelemetryPartDeleteRequest request)
    {
        var query = db.Set<TelemetryMessage>()
            .Where(o => o.IdTelemetry == request.IdTelemetry);

        if (request.GeDate is not null)
        {
            var geDate = request.GeDate.Value.ToUniversalTime();
            query = query.Where(o => o.DateTime <= geDate);
        }

        if (request.LeDate is not null)
        {
            var leDate = request.LeDate.Value.ToUniversalTime();
            query = query.Where(o => o.DateTime >= leDate);
        }

        return query;
    }

    public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
    {
        var query = BuildQuery(request);
        db.Set<TelemetryMessage>().RemoveRange(query);
        return await db.SaveChangesAsync(token);
    }
}