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.WellOperationService
{
    public class WellOperationService : IWellOperationService
    {
        private readonly IAsbCloudDbContext db;
        private readonly IWellService wellService;
        private readonly CacheTable<WellOperationCategory> cachedOperationCategories;
        private readonly CacheTable<WellSectionType> cachedSectionTypes;

        public const int idOperationBhaAssembly = 1025;
        public const int idOperationBhaDisassembly = 1026;
        public const int idOperationNonProductiveTime = 1043;
        public const int idOperationDrilling = 1001;
        public const int idOperationBhaDown = 1046;
        public const int idOperationBhaUp = 1047;
        public const int idOperationCasingDown = 1048;
        public const int idOperationTypePlan = 0;
        public const int idOperationTypeFact = 1;

        public WellOperationService(IAsbCloudDbContext db, CacheDb cache, IWellService wellService)
        {
            this.db = db;
            this.wellService = wellService;
            cachedOperationCategories = cache.GetCachedTable<WellOperationCategory>((DbContext)db);
            cachedSectionTypes = cache.GetCachedTable<WellSectionType>((DbContext)db);
        }

        public IDictionary<int, string> GetSectionTypes()
            => cachedSectionTypes.ToDictionary(s => s.Id, s => s.Caption);

        public IEnumerable<WellOperationCategoryDto> GetCategories()
        {
            var operationTypes = cachedOperationCategories
                .Distinct().OrderBy(o => o.Name);
            var result = operationTypes.Adapt<WellOperationCategoryDto>();

            return result;
        }

        public async Task<PaginationContainer<WellOperationDto>> GetOperationsAsync(
            int idWell,
            int? operationType = default,
            IEnumerable<int> sectionTypeIds = default,
            IEnumerable<int> operationCategoryIds = default,
            DateTime begin = default,
            DateTime end = default,
            double minDepth = double.MinValue,
            double maxDepth = double.MaxValue,
            int skip = 0,
            int take = 32,
            CancellationToken token = default)
        {
            var timezone = wellService.GetTimezone(idWell);

            var query = db.WellOperations
                .Include(s => s.WellSectionType)
                .Include(s => s.OperationCategory)
                .Where(s => s.IdWell == idWell);

            var dateStart = query.Min(o => o.DateStart);

            if (operationType != default)
                query = query.Where(e => e.IdType == (int)operationType);

            if (sectionTypeIds != default && sectionTypeIds.Any())
                query = query.Where(e => sectionTypeIds.Contains(e.IdWellSectionType));

            if (operationCategoryIds != default && operationCategoryIds.Any())
                query = query.Where(e => operationCategoryIds.Contains(e.IdCategory));

            if (minDepth != double.MinValue)
                query = query.Where(e => e.DepthEnd >= minDepth);

            if (maxDepth != double.MaxValue)
                query = query.Where(e => e.DepthEnd <= maxDepth);

            if (begin != default)
            {
                var beginOffset = begin.ToUtcDateTimeOffset(timezone.Hours);
                query = query.Where(e => e.DateStart >= beginOffset);
            }                

            if (end != default)
            {

                var endOffset = end.ToUtcDateTimeOffset(timezone.Hours);
                query = query.Where(e => e.DateStart <= endOffset);
            }                

            var result = new PaginationContainer<WellOperationDto>
            {
                Skip = skip,
                Take = take,
                Count = await query.CountAsync(token).ConfigureAwait(false),
            };

            query = query
                .OrderBy(e => e.DateStart)
                .ThenBy(e => e.DepthEnd)
                .ThenBy(e => e.Id);

            if (skip > 0)
                query = query.Skip(skip);

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

            var nptHours = 0d;

            foreach (var entity in entities)
            {
                var dto = entity.Adapt<WellOperationDto>();
                dto.Day = (entity.DateStart - dateStart).TotalDays;
                dto.WellSectionTypeName = entity.WellSectionType.Caption;
                dto.DateStart = entity.DateStart.ToRemoteDateTime(timezone.Hours);
                dto.CategoryName = entity.OperationCategory.Name;
                if(entity.IdType == idOperationTypeFact)
                {
                    if(entity.IdCategory == idOperationNonProductiveTime)
                        nptHours += entity.DurationHours;
                    dto.NptHours = nptHours;                    
                }
                result.Items.Add(dto);
            }

            return result;
        }

        public async Task<WellOperationDto> GetAsync(int id,
            CancellationToken token = default)
        {

            var entity = await db.WellOperations
                .Include(s => s.WellSectionType)
                .Include(s => s.OperationCategory)
                .FirstOrDefaultAsync(e => e.Id == id, token)
                .ConfigureAwait(false);

            if (entity is null)
                return null;

            var timezone = wellService.GetTimezone(entity.IdWell);

            var dto = entity.Adapt<WellOperationDto>();
            dto.WellSectionTypeName = entity.WellSectionType.Caption;
            dto.DateStart = entity.DateStart.ToRemoteDateTime(timezone.Hours);
            dto.CategoryName = entity.OperationCategory.Name;
            return dto;
        }

        public async Task<int> InsertRangeAsync(int idWell,
            IEnumerable<WellOperationDto> wellOperationDtos,
            CancellationToken token = default)
        {
            var timezone = wellService.GetTimezone(idWell);
            foreach (var dto in wellOperationDtos)
            {
                var entity = dto.Adapt<WellOperation>();
                entity.Id = default;
                entity.DateStart = dto.DateStart.ToUtcDateTimeOffset(timezone.Hours);
                entity.IdWell = idWell;
                db.WellOperations.Add(entity);
            }

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

        public async Task<int> UpdateAsync(int idWell, int idOperation,
            WellOperationDto dto, CancellationToken token = default)
        {
            var timezone = wellService.GetTimezone(idWell);
            var entity = dto.Adapt<WellOperation>();
            entity.Id = idOperation;
            entity.DateStart = dto.DateStart.ToUtcDateTimeOffset(timezone.Hours);
            db.WellOperations.Update(entity);
            return await db.SaveChangesAsync(token)
                .ConfigureAwait(false);
        }

        public async Task<int> DeleteAsync(IEnumerable<int> ids,
            CancellationToken token = default)
        {
            var query = db.WellOperations.Where(e => ids.Contains(e.Id));
            db.WellOperations.RemoveRange(query);
            return await db.SaveChangesAsync(token)
                .ConfigureAwait(false);
        }
    }
}