using AsbCloudApp.Data;
using AsbCloudApp.Data.User;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Requests;
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 WellContactService : IWellContactService
{
    private readonly IAsbCloudDbContext db;

    public WellContactService(IAsbCloudDbContext db)
    {
        this.db = db;
    }

    public async Task<IEnumerable<ContactDto>> GetAllAsync(WellContactRequest request, CancellationToken token)
    {
        var query = db.Contacts
            .Where(c => request.IdsWells.Contains(c.IdWell));

        if (request.ContactTypeId.HasValue)
        {
            query = query.Where(c => c.IdCompanyType == request.ContactTypeId);
        };

        var entities = await query.AsNoTracking()
          .ToArrayAsync(token);

        var dtos = entities.Select(c => c.Adapt<ContactDto>());

        return dtos;
    }

    public async Task<ContactDto?> GetAsync(int idWell, int id, CancellationToken token)
    {
        var dbContact = await GetContact(idWell, id, token);

        var result = dbContact?.Adapt<ContactDto>();

        return result;
    }

    public async Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(CancellationToken token)
    {
        var query = db.CompaniesTypes
            .Where(t => t.IsContact)
            .OrderBy(t => t.Order);

        var entities = await query.AsNoTracking()
           .ToArrayAsync(token);

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

        return dtos;
    }

    public async Task<int> InsertAsync(ContactDto contactDto, CancellationToken token)
    {
        var entity = contactDto.Adapt<Contact>();

        db.Contacts.Add(entity);

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


    public async Task<int> UpdateAsync(ContactDto contactDto, CancellationToken token)
    {
        var dbContact = await GetContact(contactDto.IdWell, contactDto.Id, token);
        if (dbContact is null)
            throw new ForbidException("Contact doesn't exist");

        var entity = contactDto.Adapt<Contact>();
        db.Contacts.Update(entity);

        return await db.SaveChangesAsync(token);
    }

    public async Task<int> DeleteAsync(int idWell, int id, CancellationToken token)
    {
        var dbContact = await GetContact(idWell, id, token);
        if (dbContact is null)
            throw new ForbidException("Contact doesn't exist");

        db.Contacts.Remove(dbContact);
        return await db.SaveChangesAsync(token);
    }

    public async Task<int> CopyAsync(int idWell, int idWellTarget, IEnumerable<int> contactIds, CancellationToken token)
    {
        var contacts = await GetContacts(idWell, contactIds, token);
        if (!contacts.Any())
            return 0;

        var newContacts = contacts.Select(contact =>
        {
            var newContact = contact.Adapt<Contact>();
            newContact.IdWell = idWellTarget;
            newContact.Id = default;

            return newContact;
        });

        db.Contacts.AddRange(newContacts);

        return await db.SaveChangesAsync(token);
    }

    private async Task<Contact?> GetContact(int idWell, int idContact, CancellationToken token)
    {
        var contact = await db.Contacts
            .Where(c => c.IdWell == idWell)
            .Where(c => c.Id == idContact)
            .AsNoTracking()
            .FirstOrDefaultAsync(token);

        return contact;
    }

    private async Task<Contact[]> GetContacts(int idWell, IEnumerable<int> contactIds, CancellationToken token)
    {
        var contacts = await db.Contacts
            .Where(c => c.IdWell == idWell)
            .Where(c => contactIds.Contains(c.Id))
            .AsNoTracking()
            .ToArrayAsync(token);

        return contacts;
    }
}