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 WellService : IWellService
    {
        private readonly IAsbCloudDbContext db;
        private readonly ITelemetryService telemetryService;
        private readonly CacheTable<RelationCompanyWell> cacheRelationCompaniesWells;
        private readonly CacheTable<Well> cacheWells;

        public WellService(IAsbCloudDbContext db, ITelemetryService telemetryService, CacheDb cacheDb)
        {
            this.db = db;
            this.telemetryService = telemetryService;
            cacheRelationCompaniesWells = cacheDb.GetCachedTable<RelationCompanyWell>((AsbCloudDbContext)db);
            cacheWells = cacheDb.GetCachedTable<Well>((AsbCloudDbContext)db);
        }

        public DateTime GetLastTelemetryDate(int idWell)
        {
            var well = cacheWells.FirstOrDefault(w => w.Id == idWell);
            
            if (well?.IdTelemetry is null)
                return DateTime.MinValue;

            var lastTelemetryDate = telemetryService.GetLastTelemetryDate((int)well.IdTelemetry);
            return lastTelemetryDate;
        }

        public async Task<IEnumerable<WellDto>> GetTransmittingWellsAsync(int idCompany,
            CancellationToken token)
        {
            var activeTelemetryIds = telemetryService.GetTransmittingTelemetries()
                .Select(t => t.Id);

            var wells = await (from w in db.GetWellsForCompany(idCompany)
                      where w.IdTelemetry != null && 
                          activeTelemetryIds.Contains((int)w.IdTelemetry)
                      select w)
                     .AsNoTracking()
                     .ToListAsync(token)
                     .ConfigureAwait(false);
            
            return wells.Select(Convert);
        }

        public async Task<IEnumerable<WellDto>> GetWellsByCompanyAsync(int idCompany, CancellationToken token)
        {
            var wells = await db.GetWellsForCompany(idCompany).ToListAsync(token);
            return wells.Select(Convert);
        }
        
        public async Task<int?> UpdateWellAsync(int idWell, WellParamsDto dto, 
            CancellationToken token = default)
        {
            if (dto.IdWellType is < 1 or > 2)
                throw new ArgumentException("Тип скважины указан неправильно.", nameof(dto));
            
            if (dto.IdState is < 0 or > 2)
                throw new ArgumentException("Текущее состояние работы скважины указано неправильно.", nameof(dto));

            var entity = await db.Wells
                .FirstOrDefaultAsync(w => w.Id == idWell, token)
                .ConfigureAwait(false);

            if (entity is null)
                throw new ArgumentException("Тип секции указан неправильно.", nameof(idWell));

            entity.Caption = dto.Caption;
            entity.Latitude = dto.Latitude;
            entity.Longitude = dto.Longitude;
            entity.IdWellType = dto.IdWellType;
            entity.IdState = dto.IdState;

            db.Wells.Update(entity);

            return await db.SaveChangesAsync(token);
        }

        public bool IsCompanyInvolvedInWell(int idCompany, int idWell)
        => cacheRelationCompaniesWells.Contains(r => r.IdWell == idWell && r.IdCompany == idCompany);

        public async Task<bool> IsCompanyInvolvedInWellAsync(int idCompany, int idWell, CancellationToken token)
         => await cacheRelationCompaniesWells.ContainsAsync(r => r.IdWell == idWell &&
                r.IdCompany == idCompany, token).ConfigureAwait(false);

        public async Task<WellDto> GetAsync(int idWell, CancellationToken token)
        {
            var entity = await db.Wells
                .Include(w => w.Cluster)
                .ThenInclude(c => c.Deposit)
                .FirstOrDefaultAsync(w => w.Id == idWell, token)
                .ConfigureAwait(false);
            
            if (entity is null) 
                return null;
            
            var dto = Convert(entity);

            return dto;
        }

        public async Task<string> GetWellCaptionByIdAsync(int idWell, CancellationToken token)
        {
            var entity = await cacheWells.FirstOrDefaultAsync(w => w.Id == idWell, token).ConfigureAwait(false);
            var dto = Convert(entity);
            return dto.Caption;
        }

        public async Task<IEnumerable<CompanyDto>> GetCompaniesAsync(int idWell, CancellationToken token)
        {
            var well = await db.Wells
                .Include(w => w.RelationCompaniesWells)
                .ThenInclude(r => r.Company)
                .FirstOrDefaultAsync(w => w.Id == idWell, token)
                .ConfigureAwait(false);
            var companies = well.RelationCompaniesWells.Select(r => r.Company);
            return companies.Adapt<CompanyDto>();
        }

        public string GetStateText(int state)
        {
            return state switch
            {
                1 => "В работе",
                2 => "Завершена",
                _ => "Незвестно",
            };
        }
        
        public async Task<IEnumerable<int>> GetClusterWellsIdsAsync(int idWell, CancellationToken token)
        {
            var well = await cacheWells.FirstOrDefaultAsync(w => w.Id == idWell, token)
                    .ConfigureAwait(false);
    
            if (well is null) 
                return null;
                
            var clusterWells = await cacheWells.WhereAsync(w => w.IdCluster == well.IdCluster, token)
                            .ConfigureAwait(false);
                   
            return clusterWells.Select(w => w.Id);
        }

        private WellDto Convert(Well well)
        {
            return new WellDto
            {
                Id = well.Id,
                Caption = well.Caption,
                IdCluster = well.IdCluster,
                Cluster = well.Cluster?.Caption,
                Deposit = well.Cluster?.Deposit?.Caption,
                LastTelemetryDate = GetLastTelemetryDate(well.Id),
                IdWellType = well.IdWellType ?? default,
                IdState = well.IdState,
                Latitude = well.Latitude,
                Longitude = well.Longitude,
            };
        }
    }
}