using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services
{
    public class MeasureService : IMeasureService
    {
        private readonly IAsbCloudDbContext db;
        private readonly CacheTable<MeasureCategory> cacheCategories;

        public MeasureService(IAsbCloudDbContext db, Cache.CacheDb cacheDb)
        {
            this.db = db;
            cacheCategories = cacheDb.GetCachedTable<MeasureCategory>((DbContext)db);
        }

        public async Task<Dictionary<int, string>> GetCategoriesAsync(CancellationToken token)
        {
            var entities = await cacheCategories.WhereAsync(token).ConfigureAwait(false);
            var dto = entities.ToDictionary(e => e.Id, e => e.Name);
            return dto;
        }

        public async Task<MeasureDto> GetLastAsync(int idWell, int idCategory, CancellationToken token)
        {
            var query = db.Measures
                .Include(m => m.Category)
                .Where(m => m.IdWell == idWell && m.IdCategory == idCategory && !m.IsDeleted)
                .OrderByDescending(m => m.Timestamp)
                .Take(1);

            var entity = await query
                .AsNoTracking()
                .FirstOrDefaultAsync(token)
                .ConfigureAwait(false);

            var dtos = entity?.Adapt<MeasureDto, Measure>((d, s) => d.CategoryName = s.Category?.Name);
            return dtos;
        }

        public async Task<IEnumerable<MeasureDto>> GetAllLastAsync(int idWell, CancellationToken token)
        {
            var categories = await cacheCategories.WhereAsync(token).ConfigureAwait(false);
            if (!categories.Any())
                return default;

            var queries = categories.Select(c => db.Measures.Include(m => m.Category)
                .Where(m => m.IdWell == idWell && m.IdCategory == c.Id && !m.IsDeleted)
                .OrderByDescending(m => m.Timestamp)
                .Take(1)
            );

            var qi = queries.GetEnumerator();
            qi.MoveNext();
            var query = qi.Current;
            while (qi.MoveNext())
                query = query.Union(qi.Current);

            //var query = db.Measures
            //    .Where(e => e.IdWell == idWell)
            //    .GroupBy(e => e.IdCategory)
            //    .Select(g => g.Where(e => e.Timestamp == g.Max(m => m.Timestamp)).First());

            //var query = db.Measures
            //    .Where(m => m.IdWell == idWell && m.Timestamp == db.Measures
            //        .Where(subm => subm.IdWell == m.IdWell && subm.IdCategory == m.IdCategory).Max(subm => subm.Timestamp));

            var entities = await query
                .AsNoTracking()
                .ToListAsync(token)
                .ConfigureAwait(false);

            var dtos = entities.Adapt<MeasureDto, Measure>((d, s) => d.CategoryName = s.Category?.Name);
            return dtos;
        }

        public async Task<IEnumerable<MeasureDto>> GetHisoryAsync(int idWell, int idCategory, CancellationToken token)
        {
            var query = db.Measures.Include(m => m.Category)
                .Where(m => m.IdWell == idWell && m.IdCategory == idCategory && !m.IsDeleted);

            var entities = await query
                .AsNoTracking()
                .ToListAsync(token)
                .ConfigureAwait(false);

            var dtos = entities.Adapt<MeasureDto, Measure>((d, s) => d.CategoryName = s.Category?.Name);
            return dtos;
        }

        public Task<int> InsertAsync(int idWell, MeasureDto data, CancellationToken token)
        {
            if (data.IdCategory < 1)
                throw new ArgumentException("wrong idCategory", nameof(data));
            if (data.Data is null)
                throw new ArgumentException("data.data is not optional", nameof(data));
            var entity = data.Adapt<Measure>();
            entity.Id = 0;
            entity.IdWell = idWell;
            db.Measures.Add(entity);
            return db.SaveChangesAsync(token);
        }

        public async Task<int> UpdateAsync(int idWell, MeasureDto data, CancellationToken token)
        {
            if (data.Id < 1)
                throw new ArgumentException("wrong id", nameof(data));
            if (data.IdCategory < 1)
                throw new ArgumentException("wrong idCategory", nameof(data));
            if (data.Data is null)
                throw new ArgumentException("data.data is not optional", nameof(data));

            var entity = await db.Measures
                .Where(m => m.Id == data.Id && !m.IsDeleted)
                .FirstOrDefaultAsync(token)
                .ConfigureAwait(false);

            if (entity is null)
                throw new ArgumentException("id doesn't exist", nameof(data));

            entity.Timestamp = data.Timestamp;
            entity.Data = data.Data;

            return await db.SaveChangesAsync(token).ConfigureAwait(false);
        }

        public async Task<int> MarkAsDeleteAsync(int idWell, int idData, CancellationToken token)
        {
            if (idData < 1)
                throw new ArgumentException("wrong id", nameof(idData));

            var entity = await db.Measures.Where(m => m.IdWell == idWell && m.Id == idData)
                .FirstOrDefaultAsync(token)
                .ConfigureAwait(false);

            entity.IsDeleted = true;

            return await db.SaveChangesAsync(token).ConfigureAwait(false);
        }

        public Task<int> DeleteAsync(int idWell, int idData, CancellationToken token)
        {
            if (idData < 1)
                throw new ArgumentException("wrong id", nameof(idData));
            db.Measures.RemoveRange(db.Measures.Where(m => m.IdWell == idWell && m.Id == idData));
            return db.SaveChangesAsync(token);
        }
    }
}