using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Serilog;
using System.Text.RegularExpressions;

namespace adas_core.Infrastructure.Repositories;

public class RelayRepository : MongoRepository<Relay>, IRelayRepository
{
    private readonly ApiSettings _apiSettings;
    
  

    public RelayRepository(IMongoDatabase database, IOptions<ApiSettings> apiSettings) : base(database)
    {
        _apiSettings = apiSettings.Value;
    }

    public override string GetCollectionName()
    {
        return _apiSettings.Relays;
    }

    public async Task<Relay?> GetById(ObjectId relayId)
    {
        try
        {
            var filter = Builders<Relay>.Filter.Eq(x => x.Id, relayId);
            var result = await Collection.FindAsync(filter, null);
            return result.FirstOrDefault();
        }
        catch (Exception e)
        {
            Log.Error("Exception trying to get relay by id: {id}. Exception {e}", relayId, e);
            return null;
        }
    }

    public List<Relay> GetRelayByTypeInList(List<ObjectId> configurationRelayList, RelayEnum.Type type)
    {
        var filterBuilder = Builders<Relay>.Filter;

        var filter = filterBuilder.And(
            filterBuilder.In(r => r.Id, configurationRelayList),
            filterBuilder.Eq(r => r.Type, type)
        );

        return Collection.Find(filter).ToList();
    }

    public List<Relay> GetRelayInList(List<ObjectId> configurationRelayList)
    {
        var filterBuilder = Builders<Relay>.Filter;

        var filter = filterBuilder.And(
            filterBuilder.In(r => r.Id, configurationRelayList));

        return Collection.Find(filter).ToList();
    }

    public IFindFluent<Relay, Relay> GetPaginatedRelays(PaginationFilter filter)
    {
        var filterBuilder = Builders<Relay>.Filter;
        var sort = Builders<Relay>.Sort.Ascending("relayName");
        var filters = new List<FilterDefinition<Relay>>();

        if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort);

        if (!string.IsNullOrEmpty(filter.FilteredRequest?.Text))
        {
            var textFilter = filter.FilteredRequest.Text;
            var textFilterEscaped = Regex.Escape(textFilter);

            filters.Add(
                filterBuilder.Or(
                    filterBuilder.Regex(p => p.RelayName,
                        new BsonRegularExpression(textFilterEscaped, "i"))
                )
            );
        }

        return CreateFindFluent(filters, sort);
    }

    public async Task<Relay?> InsertOneRelayAsync(Relay request)
    {
        try
        {
            await Collection.InsertOneAsync(request);
            return await GetById(request.Id);
        }
        catch (Exception ex)
        {
            Log.Error("Error inserting relay: {relay}. Exception: {ex}",
                JsonConvert.SerializeObject(request, Formatting.Indented), ex);

            return null;
        }
    }

    public async Task<Relay?> UpdateRelayAsync(ObjectId objectId, Relay relay)
    {
        var filter = Builders<Relay>.Filter.Eq("_id", objectId);
        var update = Builders<Relay>.Update
            .Set(c => c.Mode, relay.Mode)
            .Set(c => c.RelayNumber, relay.RelayNumber)
            .Set(c => c.Username, relay.Username)
            .Set(c => c.Password, relay.Password)
            .Set(c => c.Driver, relay.Driver)
            .Set(c => c.Ip, relay.Ip)
            .Set(c => c.Port, relay.Port)
            .Set(c => c.RelayName, relay.RelayName);
        return await Collection.FindOneAndUpdateAsync(filter, update,
            new FindOneAndUpdateOptions<Relay, Relay> { ReturnDocument = ReturnDocument.After });
    }

    public async Task<Relay?> GetByName(string? requestRelayName)
    {
        var filterBuilder = Builders<Relay>.Filter;

        var filter = filterBuilder.Eq(r => r.RelayName, requestRelayName);

        return await Collection.Find(filter).FirstOrDefaultAsync();
    }

    private IFindFluent<Relay, Relay> CreateFindFluent(List<FilterDefinition<Relay>> filters, SortDefinition<Relay> sort)
    {
        var combinedFilter = filters.Any()
            ? Builders<Relay>.Filter.And(filters)
            : Builders<Relay>.Filter.Empty; 
        return Collection.Find(combinedFilter).Sort(sort);
    }
}