using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;

namespace adas_core.Infrastructure.Repositories;

/// <summary>
/// Repository for managing user authorities and permissions in the system. This repository provides methods to create, retrieve, and delete authorities based on user and unit identifiers. 
/// It also includes logging for debugging purposes and ensures that initial data is loaded when necessary.
/// </summary>
/// <param name="apiSettings">The API settings containing configuration for the repository.</param>
/// <param name="database">The MongoDB database instance.</param>
/// <param name="logger">The logger for logging information, warnings, and errors.</param>
public class AuthorityRepository(
    IOptions<ApiSettings> apiSettings,
    IMongoDatabase database,
    ILogger<AuthorityRepository> logger)
    : MongoRepository<Authorization>(database), IAuthorityRepository
{
    private readonly ApiSettings _apiSettings = apiSettings.Value;
    private readonly ILogger _logger = logger;

    /// <summary>
    /// Creates a new authority for a user with the specified role. This method generates a new display ID for the authority and inserts it into the database. It also logs the creation of the new authority for debugging purposes.
    /// </summary>
    /// <param name="roleName">The name of the role to be assigned to the new authority.</param>
    /// <param name="userId">The ID of the user for whom the new authority is being created.</param>
    public void CreateNewAuthority(string roleName, ObjectId userId)
    {
        var newAuthorization = new Authorization
        {
            UserId = userId,
            DisplayId = ObjectId.GenerateNewId().ToString(),
            Rol = roleName
        };

        Collection.InsertOne(newAuthorization);
        _logger.LogDebug("New authority created for user {UserId} with role {RoleName}", userId, roleName);
    }

    /// <summary>
    /// Retrieves a list of authorities associated with a specific user ID. This method queries the database for authorities that match the provided user ID and returns them as a list. 
    /// It also logs the retrieval of authorities for debugging purposes.
    /// </summary>
    /// <param name="userId">The ID of the user whose authorities are being retrieved.</param>
    /// <returns>A list of authorities associated with the specified user ID.</returns>
    public async Task<List<Authorization>> GetUserAuthorities(ObjectId userId)
    {
        var filter = Builders<Authorization>.Filter.Eq(p => p.UserId, userId);
        var result = await Collection.FindAsync(filter);
        _logger.LogDebug("Retrieved authorities for user {UserId}", userId);
        return result.ToList();
    }

    /// <summary>
    /// Retrieves a list of all authorities in the system. This method queries the database for all authority records and returns them as a list. It also logs the retrieval of all authorities for debugging purposes.
    /// </summary>
    /// <returns>A list of all authorities in the system.</returns>
    public async Task<List<Authorization>> GetAllAuthorities()
    {
        var result = await Collection.FindAsync(Builders<Authorization>.Filter.Empty);
        _logger.LogDebug("Retrieved all authorities");
        return result.ToList();
    }

    /// <summary>
    /// Retrieves a list of authorities associated with a specific unit ID. This method queries the database for authorities that match the provided unit ID and returns them as a list.
    /// It also logs the retrieval of authorities for debugging purposes.
    /// </summary>
    /// <param name="unitId">The ID of the unit whose authorities are being retrieved.</param>
    /// <returns>A list of authorities associated with the specified unit ID.</returns>
    public async Task<List<Authorization>> GetByUnitId(ObjectId unitId)
    {
        var filter = Builders<Authorization>.Filter.Eq(a => a.UnitId, unitId.ToString());

        var result = await Collection.FindAsync(filter);

        _logger.LogDebug("Retrieved authorities for unit {UnitId}", unitId);

        return result.ToList();
    }

    /// <summary>
    /// Deletes all authorities associated with a specific user ID. This method constructs a filter to identify authorities that match the provided user ID and deletes them from the database.
    /// It also logs the deletion of authorities for debugging purposes.
    /// </summary>
    /// <param name="userId">The ID of the user whose authorities are being deleted.</param>
    /// <returns>A boolean value indicating whether the deletion was successful.</returns>
    public async Task<bool> DeleteAllAuthoritiesByUser(ObjectId userId)
    {
        try
        {
            var filter = Builders<Authorization>.Filter.Eq(p => p.UserId, userId);
            await Collection.DeleteManyAsync(filter);

            _logger.LogDebug("Deleted all authorities for user {UserId}", userId);

            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

    /// <summary>
    /// Deletes all authorities associated with a specific unit ID. This method constructs a filter to identify authorities that match the provided unit ID and deletes them from the database.
    /// It also logs the deletion of authorities for debugging purposes.
    /// </summary>
    /// <param name="unitId">The ID of the unit whose authorities are being deleted.</param>
    /// <returns>A boolean value indicating whether the deletion was successful.</returns>
    public async Task<bool> DeleteAllAuthoritiesByUnit(ObjectId unitId)
    {
        try
        {
            var filter = Builders<Authorization>.Filter.Eq(p => p.UnitId, unitId.ToString());
            await Collection.DeleteManyAsync(filter);
            _logger.LogDebug("Deleted all authorities for unit {UnitId}", unitId);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

    /// <summary>
    /// Deletes all authorities associated with a specific display ID. This method constructs a filter to identify authorities that match the provided display ID and deletes them from the database.
    /// It also logs the deletion of authorities for debugging purposes.
    /// </summary>
    /// <param name="displayId">The ID of the display whose authorities are being deleted.</param>
    /// <returns>A boolean value indicating whether the deletion was successful.</returns>
    public async Task<bool> DeleteAllAuthoritiesByDisplay(ObjectId displayId)
    {
        try
        {
            var filter = Builders<Authorization>.Filter.Eq(p => p.DisplayId, displayId.ToString());
            await Collection.DeleteManyAsync(filter);
            _logger.LogDebug("Deleted all authorities for display {DisplayId}", displayId);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

    /// <summary>
    /// Retrieves the name of the collection used for storing authorities in the database. This method returns the collection name as specified in the API settings configuration.
    /// </summary>
    /// <returns></returns>
    public override string GetCollectionName()
    {
        return _apiSettings.Authorizations;
    }

    /// <summary>
    /// Retrieves an authority by its unique identifier. This method constructs a filter to identify the authority that matches the provided ID and retrieves it from the database. 
    /// It also logs the retrieval of the authority for debugging purposes.
    /// </summary>
    /// <param name="authId">The unique identifier of the authority to be retrieved.</param>
    /// <returns>The authority that matches the provided ID, or null if no matching authority is found.</returns>
    public async Task<Authorization> GetById(ObjectId authId)
    {
        var filter = Builders<Authorization>.Filter.Eq(p => p.Id, authId);
        return await Collection.Find(filter).FirstOrDefaultAsync();
    }

    /// <summary>
    /// Inserts initial data into the database for authorities. This method checks if a system user exists and if they do, it ensures that they have the necessary panel authorization. 
    /// If the system user does not have panel authorization, it creates a new authority for them with the appropriate permissions. 
    /// This method is typically called during the initial setup of the application to ensure that essential data is present in the database.
    /// </summary>
    /// <returns>A task representing the asynchronous operation.</returns>
    public sealed override async Task InsertInitialLoad()
    {
        var user = Db.GetCollection<User>(apiSettings.Value.Users);
        var filter = Builders<User>.Filter.Eq(p => p.UserName, "System");
        var search = await user.FindAsync(filter);
        var systemUSer = search.FirstOrDefault();
        if (systemUSer != null)
        {
            var auths = await GetUserAuthorities(systemUSer.Id);
            if (!auths.Exists(c => c.PanelAuthorization))
                await InsertOneAsync(new Authorization
                    { CanUpdate = false, PanelAuthorization = true, Rol = "AuthAdmin", UserId = systemUSer.Id });
        }
    }
}