using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.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 System.Text.RegularExpressions;

namespace adas_core.Infrastructure.Repositories;

public class ConfigObservationRepository : MongoRepository<ConfigObservation>, IConfigObservationRepository
{
    private readonly ApiSettings _apiSettings;
    private readonly IMasterListServiceFactory _masterListServiceFactory;

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

    public override string GetCollectionName()
    {
        return _apiSettings.ConfigObservations ?? "config_observations";
    }

    public async Task<ConfigObservation?> FindById(ObjectId id)
    {
        var result = await Collection.FindAsync(Builders<ConfigObservation>.Filter.Eq(x => x.Id, id));
        return await result.FirstOrDefaultAsync();
    }

    public async Task<ConfigObservation?> Update(ConfigObservation configObservation)
    {
        await UpdateOneAsync(configObservation.Id, configObservation);
        return configObservation;
    }

    public async Task<ConfigObservation?> Delete(ObjectId id)
    {
        return await DeleteAsync(id);
    }

    public async Task<List<ObjectId>> FindAllIds()
    {
        var allCollection = await Collection.FindAsync(_ => true);

        return allCollection.ToList().Select(item => item.Id).ToList();
    }

    public async Task<ICollection<ConfigObservation>> FindAll()
    {
        var result = await Collection.FindAsync(_ => true);
        return await result.ToListAsync();
    }

    public async Task<List<string>> GetConfigNames(string id)
    {
        var allConfigs = await Collection.Find(_ => true).ToListAsync();

        var distinctNames = allConfigs
            .Where(item => !string.IsNullOrWhiteSpace(item.Name))
            .Select(item => item.Name!.Trim())
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();

        return distinctNames;
    }

    public async Task<List<string>> GetConfigNames()
    {
        var allConfigs = await Collection.Find(_ => true).ToListAsync();

        var distinctNames = allConfigs
            .Where(item => !string.IsNullOrWhiteSpace(item.Name))
            .Select(item => item.Name!.Trim())
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();

        return distinctNames;
    }

    public async Task<long> Count()
    {
        var filter = Builders<ConfigObservation>.Filter.Empty;
        var result = await Collection.CountDocumentsAsync(filter);
        return result;
    }

    public async Task<ICollection<ConfigObservation>> GetPaginatedItems(PaginationFilter filter)
    {
        var builder = Builders<ConfigObservation>.Filter;
        var filterDefinition = builder.Empty;

        if (!string.IsNullOrWhiteSpace(filter.FilteredRequest?.Text))
        {
            var searchText = filter.FilteredRequest.Text;

            if (searchText.Length > 100)
                throw new BadRequestException("Search text too long");

            var searchTextEscaped = Regex.Escape(searchText);

            var regex = new BsonRegularExpression(searchTextEscaped, "i");

            var searchFilters = new List<FilterDefinition<ConfigObservation>>
        {
            builder.Regex(x => x.Name, regex),
            builder.Regex(x => x.CodingSystem, regex),
            builder.Regex(x => x.OriginalName, regex),
            builder.Regex(x => x.Code, regex),
            builder.Regex("uiConfiguration.screenLabel", regex)
        };

            filterDefinition = builder.Or(searchFilters);
        }

        var sortDefinition = Builders<ConfigObservation>.Sort.Ascending(x => x.Name);

        var skip = Math.Max(0, (filter.PageNumber - 1) * filter.PageSize);
        var limit = Math.Min(filter.PageSize, 100); // límite defensivo

        var items = await Collection
            .Find(filterDefinition)
            .Sort(sortDefinition)
            .Skip(skip)
            .Limit(limit)
            .ToListAsync();

        return items;
    }
    public async Task<ConfigObservation?> FindByName(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);

        if (name.Length > 100)
            throw new BadRequestException("Name too long");

        var safeName = Regex.Escape(name);

        var filter = Builders<ConfigObservation>.Filter.Regex(
            x => x.Name,
            new BsonRegularExpression($"^{safeName}$", "i")
        );

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


    public async Task<ConfigObservation?> GetByCodeSysAndCode(string? codingSystem, string? code)
    {
        var builder = Builders<ConfigObservation>.Filter;
        var filter = builder.Empty;

        if (!string.IsNullOrEmpty(codingSystem) && !string.IsNullOrEmpty(code))
        {
            filter &= builder.Eq(x => x.CodingSystem, codingSystem);
            filter &= builder.Eq(x => x.Code, code);
        }

        if (filter == builder.Empty) return null;

        var result = await Collection.FindAsync(filter);

        return await result.FirstOrDefaultAsync();
    }

    public async Task<ConfigObservation> InsertOneAsyncAndReturn(ConfigObservation configObservationItem)
    {
        await Collection.InsertOneAsync(configObservationItem);
        return configObservationItem;
    }

    public async Task<List<ConfigObservation>> FindAllByName(string name)
    {
        var builder = Builders<ConfigObservation>.Filter;
        var filter = builder.Empty;

        if (!string.IsNullOrEmpty(name)) filter &= builder.Eq(x => x.Name, name);

        if (filter == builder.Empty) return [];

        var result = await Collection.FindAsync(filter);

        return await result.ToListAsync();
    }

    public async Task<ConfigObservation?> GetSingleConfigObservationItem(string? code, string? codingSystem,
        string? name, string? originalName)
    {
        var builder = Builders<ConfigObservation>.Filter;
        var filterDefinition = builder.Empty;
        if (!string.IsNullOrEmpty(codingSystem)) filterDefinition &= builder.Eq(x => x.CodingSystem, codingSystem);

        if (!string.IsNullOrEmpty(code)) filterDefinition &= builder.Eq(x => x.Code, code);

        if (!string.IsNullOrEmpty(name)) filterDefinition &= builder.Eq(x => x.Name, name);

        if (!string.IsNullOrEmpty(originalName)) filterDefinition &= builder.Eq(x => x.OriginalName, originalName);
        return await Collection
            .Find(filterDefinition)
            .FirstOrDefaultAsync();
    }

    public sealed override async Task InsertInitialLoad()
    {
        var stringNurseObs = _masterListServiceFactory.StringNurseObs();
        var isInitialized = await FindAll();

        if (isInitialized.Count > 0)
        {
            var existingNames = isInitialized
                .Where(c => c.Name != null) // Filtrar nulls para evitar errores de referencia
                .Select(c => c.Name!) // Seleccionar solo los nombres (usamos '!' si confías en el filtro anterior)
                .ToList();
            var missingItems = stringNurseObs
                .Where(x => !existingNames.Contains(x))
                .ToList();

            foreach (var item in missingItems)
                await InsertOneAsync(new ConfigObservation
                {
                    Name = item, Code = "NURSE-ADAS", CodingSystem = "ADAS",
                    InsertMode = ObservationEnum.InsertMode.Manual
                });
            return;
        }

        var masterListObsConfig = stringNurseObs.Select(c => new ConfigObservation
            {
                Name = c, Code = "NURSE-ADAS", CodingSystem = "ADAS", InsertMode = ObservationEnum.InsertMode.Manual
            })
            .ToList();
        foreach (var item in masterListObsConfig) await InsertOneAsync(item);
    }
}