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 System.Text.RegularExpressions;

namespace adas_core.Infrastructure.Repositories;

public class UserRepository : MongoRepository<User>, IUserRepository
{
    private readonly ApiSettings _apiSettings;

    public UserRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
    {
        if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
        _apiSettings = apiSettings.Value;
    }

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

    public async Task<User?> GetUser(string username, string password)
    {
        var filter = Builders<User>
            .Filter.Eq(p => p.UserName, username) & Builders<User>
            .Filter.Eq(p => p.Password, password);
        var result = await Collection.FindAsync(filter);
        return await result.FirstOrDefaultAsync();
    }

    public async Task<User?> GetById(ObjectId id)
    {
        var filter = Builders<User>
            .Filter.Eq(p => p.Id, id);
        var result = await Collection.FindAsync(filter);
        return await result.FirstOrDefaultAsync();
    }

    public async Task<User?> GetByUserName(string name)
    {
        var filter = Builders<User>
            .Filter.Eq(p => p.UserName, name);
        var result = await Collection.FindAsync(filter);
        return await result.FirstOrDefaultAsync();
    }

    public async Task<User?> GetByUserAndAuthoritesName(string name)
    {
        var matchStage = new BsonDocument("$match", new BsonDocument("userName", name));
        var lookupStage = new BsonDocument("$lookup", new BsonDocument
        {
            { "from", "authorizations" },
            { "localField", "_id" },
            { "foreignField", "userId" },
            { "as", "Authorization" }
        });

        var projectStage = new BsonDocument("$project", new BsonDocument
        {
            { "_id", 1 },
            { "userName", 1 },
            { "email", 1 },
            { "name", 1 },
            { "Authorization", "$Authorization" },
            {
                "rol", new BsonDocument("$cond", new BsonArray
                {
                    new BsonDocument("$eq", new BsonArray { "$Authorization.rol", "Admin" }),
                    "$rol",
                    "Some"
                })
            }
        });

        var pipeline = new[]
        {
            matchStage,
            lookupStage,
            projectStage
        };

        var options = new AggregateOptions { AllowDiskUse = true };
        var result = await Collection.AggregateAsync<User>(pipeline, options);
        var bsonResult = await result.FirstOrDefaultAsync();

        return bsonResult;
    }

    public async Task<User?> GetByName(string name)
    {
        var filter = Builders<User>
            .Filter.Eq(p => p.Name, name);
        var result = await Collection.FindAsync(filter);
        return await result.FirstOrDefaultAsync();
    }

    public async Task<User?> UpdateUser(User user, bool updatePass)
    {
        var filter = Builders<User>
            .Filter.Eq(p => p.Id, user.Id);
        var update = Builders<User>.Update
            .Set(u => u.UserName, user.UserName)
            .Set(u => u.Name, user.Name)
            .Set(u => u.Email, user.Email)
            .Set(u => u.LockExpirationDate, user.LockExpirationDate)
            .Set(u => u.LastLogin, user.LastLogin)
            .Set(u => u.IsEnabled, user.IsEnabled);

        if (updatePass) update = update.Set(u => u.Password, user.Password);

        await Collection.UpdateOneAsync(filter, update);
        var result = await Collection.Find(filter).FirstOrDefaultAsync();
        return result;
    }

    public IFindFluent<User, User> GetPaginatedUsers(PaginationFilter filteredRequest)
    {
        // Crear variable con la clase que construye los filtros que necesitamos
        var filterBuilder = Builders<User>.Filter;
        var sort = Builders<User>.Sort.Ascending("userName");
        // Crear una lista de filtros que pueden venir de tu servicio
        var filters = new List<FilterDefinition<User>>();
        if (filteredRequest.FilteredRequest == null) return CreateFindFluent(filters, sort);
        var textFilter = filteredRequest.FilteredRequest?.Text;
        if (!string.IsNullOrEmpty(textFilter))
        {
            var textFilterEscaped = Regex.Escape(textFilter);
            var orFilters = new List<FilterDefinition<User>>
            {
                filterBuilder.Regex(p => p.Name, new BsonRegularExpression(textFilterEscaped, "i")),
                filterBuilder.Regex(p => p.Email, new BsonRegularExpression(textFilterEscaped, "i")),
                filterBuilder.Regex(p => p.UserName, new BsonRegularExpression(textFilterEscaped, "i"))
            };
            filters.Add(filterBuilder.Or(orFilters));
        }

        var userStatus = filteredRequest.FilteredRequest?.UserStatus;
        filters.Add(filterBuilder.And(GetUserStatusFilter(userStatus)));


        if (Enum.TryParse(filteredRequest.FilteredRequest?.UserType, out UserEnum.Type userType))
        {
            GetUserTypeFilter(userType);
            filters.Add(filterBuilder.And(GetUserTypeFilter(userType)));
        }

        return CreateFindFluent(filters, sort);
    }


    public async Task<User> GetOrCreateSystemUser()
    {
        var user = await GetByUserName("System");
        if (user == null)
        {
            var userToInsert = new User
            {
                UserName = "System",
                Name = "System",
                Password = "$2a$12$crWa3EN1izcZBXNc81RzmOlfaYW2TPr2NdDQEWI7RzLTnA0Dd68WG"
            };
            await Collection.InsertOneAsync(userToInsert);
            return userToInsert;
        }

        return user;
    }

    public sealed override async Task InsertInitialLoad()
    {
        await GetOrCreateSystemUser();
    }

    private IFindFluent<User, User> CreateFindFluent(List<FilterDefinition<User>> filters, SortDefinition<User> sort)
    {
        var combinedFilter = filters.Any()
            ? Builders<User>.Filter.And(filters)
            : Builders<User>.Filter.Empty; // Filtra todo si no hay filtros

        return Collection.Find(combinedFilter).Sort(sort);
    }

    private List<FilterDefinition<User>> GetUserTypeFilter(UserEnum.Type? type)
    {
        var filters = new List<FilterDefinition<User>>();
        var filterBuilder = Builders<User>.Filter;

        switch (type)
        {
            case UserEnum.Type.Local:
                filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Local));
                break;
            case UserEnum.Type.Ldap:
                filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Ldap));
                break;
        }

        return filters;
    }

    private List<FilterDefinition<User>> GetUserStatusFilter(StatusEnum.User? status)
    {
        var filters = new List<FilterDefinition<User>>();
        var filterBuilder = Builders<User>.Filter;
        switch (status)
        {
            case StatusEnum.User.Enabled:
                filters.Add(filterBuilder.Or(
                    filterBuilder.Eq(u => u.IsEnabled, true),
                    filterBuilder.Exists(u => u.IsEnabled, false)
                ));
                break;
            case StatusEnum.User.EnabledUnlocked:
                filters.Add(filterBuilder.Or(
                    filterBuilder.Eq(u => u.IsEnabled, true),
                    filterBuilder.Exists(u => u.IsEnabled, false)
                ));
                filters.Add(filterBuilder.Ne(u => u.LockExpirationDate, null));
                break;
            case StatusEnum.User.EnabledLocked:
                filters.Add(filterBuilder.Or(
                    filterBuilder.Eq(u => u.IsEnabled, true),
                    filterBuilder.Exists(u => u.IsEnabled, false)
                ));
                filters.Add(filterBuilder.Eq(u => u.LockExpirationDate, null));
                break;
            case StatusEnum.User.Disabled:
                filters.Add(filterBuilder.Eq(u => u.IsEnabled, false));
                break;
        }

        return filters;
    }
}