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

namespace AsbCloudInfrastructure.Services
{
    public class ClusterService : IClusterService
    {
        private readonly IAsbCloudDbContext db;
        private readonly IWellService wellService;

        public ClusterService(IAsbCloudDbContext db, IWellService wellService)
        {
            this.db = db;
            this.wellService = wellService;
        }

        public async Task<IEnumerable<DepositDto>> GetDepositsAsync(int idCompany,
            CancellationToken token = default)
        {
            var wellEntities = await (from well in db.Wells
                .Include(w => w.RelationCompaniesWells)
                .Include(w => w.WellType)
                .Include(w => w.Cluster)
                .ThenInclude(c => c.Deposit)
                                      where well.RelationCompaniesWells.Any(r => r.IdCompany == idCompany)
                                      select well).ToListAsync(token)
                                .ConfigureAwait(false);

            var gDepositEntities = GroupWells(wellEntities);

            var dtos = CreateDepositDto(gDepositEntities);

            return dtos;
        }

        public async Task<IEnumerable<DepositDto>> GetDepositsDrillParamsAsync(int idCompany,
            CancellationToken token = default)
        {
            var wellEntities = await (from well in db.Wells
                        .Include(w => w.RelationCompaniesWells)
                        .Include(w => w.WellType)
                        .Include(w => w.Cluster)
                        .ThenInclude(c => c.Deposit)
                                      from p in db.DrillParams
                                      where well.RelationCompaniesWells.Any(r => r.IdCompany == idCompany) &&
                                            well.Id == p.IdWell
                                      select well).ToListAsync(token)
                        .ConfigureAwait(false);

            var gDepositEntities = GroupWells(wellEntities);

            var dtos = CreateDepositDto(gDepositEntities);

            return dtos;
        }

        public async Task<IEnumerable<ClusterDto>> GetClustersAsync(int idCompany,
            CancellationToken token = default)
        {
            var entities = await GetWellsForCompany(idCompany)
                        .Select(e => e.Cluster)
                        .Distinct()
                        .AsNoTracking()
                        .ToListAsync(token)
                        .ConfigureAwait(false);

            var dtos = entities.Adapt<IEnumerable<ClusterDto>>();

            return dtos;
        }

        public async Task<IEnumerable<ClusterDto>> GetClustersAsync(int idCompany,
            int depositId, CancellationToken token = default)
        {
            var entities = await GetWellsForCompany(idCompany)
                        .Select(e => e.Cluster)
                        .Where(e => e.IdDeposit == depositId)
                        .Distinct()
                        .AsNoTracking()
                        .ToListAsync(token)
                        .ConfigureAwait(false);

            var dtos = entities.Adapt<IEnumerable<ClusterDto>>();

            return dtos;
        }

        public async Task<IEnumerable<WellDto>> GetWellsAsync(int idCompany,
            int idCluster, CancellationToken token = default)
        {
            var entities = await GetWellsForCompany(idCompany)
                .Where(e => e.IdCluster == idCluster)
                .AsNoTracking()
                .ToListAsync(token)
                .ConfigureAwait(false);

            var dtos = entities.Select(e => new WellDto
            {
                Id = e.Id,
                Caption = e.Caption,
                Latitude = e.Latitude,
                Longitude = e.Longitude,
                Cluster = e.Cluster.Caption,
                Deposit = e.Cluster.Deposit.Caption,
            });

            return dtos;
        }

        private static IEnumerable<IGrouping<Deposit, IGrouping<Cluster, Well>>> GroupWells(IEnumerable<Well> wellEntities)
        {
            return wellEntities
                .GroupBy(w => w.Cluster)
                .GroupBy(c => c.Key.Deposit);
        }

        private IQueryable<Well> GetWellsForCompany(int idCompany)
        {
            return db.Wells
                   .Include(w => w.RelationCompaniesWells)
                   .ThenInclude(r => r.Company)
                   .Include(w => w.Cluster)
                   .ThenInclude(c => c.Deposit)
                   .Where(w => w.RelationCompaniesWells.Any(c => c.IdCompany == idCompany));
        }

        private IEnumerable<DepositDto> CreateDepositDto(IEnumerable<IGrouping<Deposit, IGrouping<Cluster, Well>>> gDepositEntities)
        {
            return gDepositEntities.Select(gDeposit => new DepositDto
            {
                Id = gDeposit.Key.Id,
                Caption = gDeposit.Key.Caption,
                Latitude = gDeposit.Key.Latitude,
                Longitude = gDeposit.Key.Longitude,
                Clusters = gDeposit.Select(gCluster => new ClusterDto
                {
                    Id = gCluster.Key.Id,
                    Caption = gCluster.Key.Caption,
                    Latitude = gCluster.Key.Latitude,
                    Longitude = gCluster.Key.Longitude,
                    Wells = gCluster.Select(well =>
                    {
                        var dto = well.Adapt<WellDto>();
                        dto.WellType = well.WellType?.Caption;
                        dto.LastTelemetryDate = wellService.GetLastTelemetryDate(well.Id).DateTime;
                        dto.Cluster = gCluster.Key.Caption;
                        dto.Deposit = gDeposit.Key.Caption;
                        return dto;
                    }),
                }),
            });
        }
    }
}