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 DataService : IDataService
    {
        private readonly IAsbCloudDbContext db;
        private readonly ITelemetryService telemetryService;
        private readonly ITelemetryAnalyticsService analyticsService;
        private readonly CacheTable<Telemetry> cacheTelemetry;
        private readonly CacheTable<TelemetryUser> cacheTelemetryUsers;
        private readonly CacheTable<Well> cacheWells;

        public DataService(IAsbCloudDbContext db, ITelemetryService telemetryService,
            ITelemetryAnalyticsService analyticsService, CacheDb cacheDb)
        {
            this.db = db;
            this.telemetryService = telemetryService;
            this.analyticsService = analyticsService;
            cacheTelemetry = cacheDb.GetCachedTable<Telemetry>((AsbCloudDbContext)db);
            cacheTelemetryUsers = cacheDb.GetCachedTable<TelemetryUser>((AsbCloudDbContext)db);
            cacheWells = cacheDb.GetCachedTable<Well>((AsbCloudDbContext)db);
        }

        public async Task<IEnumerable<DataSaubBaseDto>> GetAsync(int idWell,
            DateTime dateBegin = default, double intervalSec = 600d,
            int approxPointsCount = 1024, CancellationToken token = default)
        {
            var well = cacheWells.FirstOrDefault(w => w.Id == idWell);
            if (well is null)
                return default;

            var telemetry = cacheTelemetry.FirstOrDefault(t => t.Id == well.IdTelemetry);
            if (telemetry is null)
                return default;

            if (dateBegin == default)
                dateBegin = DateTime.Now.AddSeconds(-intervalSec);

            var datEnd = dateBegin.AddSeconds(intervalSec);

            var query = from data in db.DataSaubBases
                        where data.IdTelemetry == telemetry.Id
                        && data.Date >= dateBegin && data.Date < datEnd
                        select data;

            var fullDataCount = await query.CountAsync(token)
                .ConfigureAwait(false);

            if (fullDataCount == 0)
                return default;

            if (fullDataCount > 1.75 * approxPointsCount)
            {
                var m = (int)Math.Round(1d * fullDataCount / approxPointsCount);
                if (m > 1)
                    query = query.Where(d => d.Id % m == 0);
            }

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

            var dtos = entities.Adapt<DataSaubBaseDto>();

            return dtos;
        }

        public async Task<int> UpdateDataAsync(string uid, IEnumerable<DataSaubBaseDto> dtos,
            CancellationToken token = default)
        {
            if (dtos == default || !dtos.Any())
                return 0;

            var telemetryId = telemetryService.GetOrCreateTemetryIdByUid(uid);
            var dtoMinDate = dtos.Min(d => d.Date);
            var dtoMaxDate = dtos.Max(d => d.Date);

            var oldDataSaubBase = await (from d in db.DataSaubBases
                                         where d.IdTelemetry == telemetryId
                                             && d.Date > dtoMinDate
                                             && d.Date < dtoMaxDate
                                         select d).AsNoTracking()
                                        .ToListAsync(token)
                                        .ConfigureAwait(false);

            if (oldDataSaubBase.Any())
                db.DataSaubBases.RemoveRange(oldDataSaubBase);

            foreach (var dto in dtos)
            {
                var dataSaub = dto.Adapt<DataSaubBase>();
                var telemetryUser = await cacheTelemetryUsers
                    .FirstOrDefaultAsync(u => u.IdTelemetry == telemetryId && (u.Name == dto.User || u.Surname == dto.User), token)
                    .ConfigureAwait(false);
                dataSaub.IdUser = telemetryUser?.IdUser;
                dataSaub.IdTelemetry = telemetryId;
                db.DataSaubBases.Add(dataSaub);

                dto.IdTelemetry = telemetryId;
                analyticsService.SaveAnalytics(dto);
            }

            return await db.SaveChangesAsync(token);
        }

        public async Task<DatesRangeDto> GetDataDatesRangeAsync(int idWell,
            CancellationToken token = default)
        {
            var telemetryId = telemetryService.GetIdTelemetryByIdWell(idWell);
            if (telemetryId is null)
                return null;

            var (From, To) = await db.GetDatesRangeAsync<DataSaubBase>((int)telemetryId, token)
                .ConfigureAwait(false);

            return new DatesRangeDto { From = From, To = To };
        }
    }
}