using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
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.Repository
{
#nullable enable
    public class DepositRepository : IDepositRepository
    {
        private readonly IAsbCloudDbContext db;
        private readonly IWellService wellService;

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

        /// <inheritdoc/>
        public async Task<IEnumerable<DepositDto>> GetAsync(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;
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<DepositDto>> GetAllWithDrillParamsAsync(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;
        }

        /// <inheritdoc/>
        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;
        }

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

        private IQueryable<Well> GetWellsForCompany(int idCompany)
        => 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)
        {
            var dtos = 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;
                    }),
                }),
            });
            return dtos;
        }
    }
#nullable disable
}