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.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;

namespace adas_core.Application.Services;

public class PermissionService(
    IOptions<PermissionSettings> permissionsConfig,
    ILogger<PermissionService> logger,
    Lazy<IDisplayService> displayService,
    Lazy<IUserRepository> userRepository,
    Lazy<IAuthorityRepository> authorityRepository)
    : IPermissionService
{
    private readonly ILogger<PermissionService> _logger = logger;
    private readonly PermissionSettings _permissionsConfig = permissionsConfig.Value;

    /// <summary>
    /// Retrieves the display permission configuration for the specified user based on their authorization roles.
    /// Loads the user's authorities from the repository if not already cached, matches the display or its unit against the authorities, and maps the role to the corresponding display permission set (Guest, Admin, Developer, DoctorChief, Doctor, NursingSupervisor, or Nurse).
    /// </summary>
    /// <param name="display">The display whose permissions are being resolved; matched against the user's authorized display and unit identifiers.</param>
    /// <param name="user">The user whose authorizations and roles are evaluated to determine the applicable display permissions.</param>
    /// <returns>A <see cref="Task{DisplayPermissionTypes}"/> containing the display permission configuration corresponding to the matched role.</returns>
    /// <exception cref="ForbbidenException">Thrown when the user has no authorizations available, or when none of the user's authorities match the given display or its unit.</exception>
    public async Task<DisplayPermissionTypes> GetPermissionsForDisplay(Display display, User user)
    {
        var authorities = user.Authorization;
        if (authorities == null)
        {
            var a = await userRepository.Value.GetByUserAndAuthoritesName(user.UserName);
            authorities = a?.Authorization;
            user.Authorization = authorities;
        }

        if (authorities == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);

        foreach (var auth in authorities)
            if (display.Id.ToString().Equals(auth.DisplayId) || display.UnitId.ToString().Equals(auth.UnitId))
            {
                _logger.LogInformation("User {user} has role {role} for display {displayId} or unit {unitId}",
                    user.UserName, auth.Rol, auth.DisplayId, auth.UnitId);

                //var unitPerms = await GetPermissionsForUnit(display.UnitId.ToString(), user);
                switch ((PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType),
                            auth.Rol ?? string.Empty))
                {
                    case PermissionEnum.RolesType.AuthGuest:
                        // return (await CheckPermissions(_permissionsConfig.Guest.Display, display.UnitId.ToString()));
                        return _permissionsConfig.Guest.Display;
                    case PermissionEnum.RolesType.AuthAdmin:
                        // return await CheckPermissions(_permissionsConfig.Admin.Display, display.UnitId.ToString());
                        return _permissionsConfig.Admin.Display;
                    case PermissionEnum.RolesType.AuthDeveloper:
                        // return await CheckPermissions(_permissionsConfig.Developer.Display, display.UnitId.ToString());
                        return _permissionsConfig.Developer.Display;
                    case PermissionEnum.RolesType.AuthDoctorchief:
                        // return await CheckPermissions(_permissionsConfig.DoctorChief.Display, display.UnitId.ToString());
                        return _permissionsConfig.DoctorChief.Display;
                    case PermissionEnum.RolesType.AuthDoctor:
                        // return await CheckPermissions(_permissionsConfig.Doctor.Display, display.UnitId.ToString());
                        return _permissionsConfig.Doctor.Display;
                    case PermissionEnum.RolesType.AuthNursesupervisor:
                        // return await CheckPermissions(_permissionsConfig.NursingSupervisor.Display, display.UnitId.ToString());
                        return _permissionsConfig.NursingSupervisor.Display;
                    case PermissionEnum.RolesType.AuthNurse:
                        // return await CheckPermissions(_permissionsConfig.Nurse.Display, display.UnitId.ToString());
                        return _permissionsConfig.Nurse.Display;
                }
            }

        //return _permissionsConfig.NoPermissions.Display;
        throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
    }

    /// <summary>
    /// Retrieves the permission set associated with a specific unit for the given user, based on the user's role within that unit.
    /// Iterates the user's authorizations to find a matching unit, parses the role, and returns the corresponding configured permissions (Guest, Admin, Developer, DoctorChief, Doctor, NursingSupervisor, or Nurse).
    /// </summary>
    /// <param name="unitId">The identifier of the unit whose permissions should be resolved.</param>
    /// <param name="user">The user whose authorizations and role are used to determine the permissions for the unit.</param>
    /// <returns>A <see cref="Task{DisplayPermissionTypes}"/> that resolves to the permission configuration matching the user's role for the specified unit.</returns>
    /// <exception cref="ForbbidenException">Thrown when the user has no authorizations, or when no authorization entry matches the provided <paramref name="unitId"/>.</exception>
    public Task<DisplayPermissionTypes> GetPermissionsForUnit(string unitId, User user)
    {
        var authorities = user.Authorization;
        if (authorities == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
        foreach (var auth in authorities)
            if (auth.UnitId != null && auth.UnitId.Equals(unitId))
            {
                _logger.LogInformation("User {user} has role {role} for unit {unitId}", user.UserName, auth.Rol,
                    auth.UnitId);

                switch ((PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType),
                            auth.Rol ?? string.Empty))
                {
                    case PermissionEnum.RolesType.AuthGuest:
                        // return await CheckPermissions(_permissionsConfig.Guest.Unit, auth.UnitId);
                        return Task.FromResult(_permissionsConfig.Guest.Unit);
                    case PermissionEnum.RolesType.AuthAdmin:
                        // return await CheckPermissions(_permissionsConfig.Admin.Unit, auth.UnitId);
                        return Task.FromResult(_permissionsConfig.Admin.Unit);
                    case PermissionEnum.RolesType.AuthDeveloper:
                        // return await CheckPermissions(_permissionsConfig.Developer.Unit, auth.UnitId);
                        return Task.FromResult(_permissionsConfig.Developer.Unit);
                    case PermissionEnum.RolesType.AuthDoctorchief:
                        // return await CheckPermissions(_permissionsConfig.DoctorChief.Unit, auth.UnitId);
                        return Task.FromResult(_permissionsConfig.DoctorChief.Unit);
                    case PermissionEnum.RolesType.AuthDoctor:
                        // return await CheckPermissions(_permissionsConfig.Doctor.Unit, auth.UnitId);
                        return Task.FromResult(_permissionsConfig.Doctor.Unit);
                    case PermissionEnum.RolesType.AuthNursesupervisor:
                        // return await CheckPermissions(_permissionsConfig.NursingSupervisor.Unit, auth.UnitId);
                        return Task.FromResult(_permissionsConfig.NursingSupervisor.Unit);
                    case PermissionEnum.RolesType.AuthNurse:
                        // return await CheckPermissions(_permissionsConfig.Nurse.Unit, auth.UnitId);
                        return Task.FromResult(_permissionsConfig.Nurse.Unit);
                }
            }

        throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
    }

    /*
            private async Task<DisplayPermissionTypes?> CheckPermissions(DisplayPermissionTypes guestUnit, string authUnitId)
            {
                var isParsed = ObjectId.TryParse(authUnitId, out var unitId);
                if(isParsed)
                {
                   var unit = await uniService.Value.FindById(unitId);
                   var unitConfiguration = unit?.Configuration;
                   if (unitConfiguration != null)
                   {
                       guestUnit.Admissions.Execute = unitConfiguration.ManualAdmit;

                       guestUnit.Discharges.Execute = unitConfiguration.ManualDischarge;

                       guestUnit.DemographicData.Create = unitConfiguration.ManualEdit;
                       guestUnit.DemographicData.Delete = unitConfiguration.ManualEdit;
                       guestUnit.DemographicData.Update = unitConfiguration.ManualEdit;
                       guestUnit.DemographicData.Execute = unitConfiguration.ManualEdit;
                   }
                }

                _logger.LogInformation("Permissions for unit {unitId}: {@permissions}", authUnitId, guestUnit);

                return guestUnit;
            }
    */

    /// <summary>
    /// Retrieves the panel permissions for the specified user, including their assigned authorities.
    /// </summary>
    /// <param name="user">The username of the user whose panel permissions are being requested.</param>
    /// <returns>The <see cref="PanelPermissionTypes"/> that apply to the user based on their authorities.</returns>
    /// <exception cref="ForbbidenException">Thrown when no user is found with the specified username.</exception>
    public async Task<PanelPermissionTypes> GetPermissionsForPanel(string user)
    {
        var userFound = await userRepository.Value.GetByUserName(user);
        if (userFound == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
        userFound.Authorization = await authorityRepository.Value.GetUserAuthorities(userFound.Id);
        return GetPermissionsForPanel(userFound);
    }
    /// <summary>
    /// Retrieves the panel permissions assigned to a user based on their authorization role.
    /// Iterates through the user's authorities, identifies the first entry with panel authorization, and maps the role to the corresponding <see cref="PanelPermissionTypes"/> configuration (Guest, Admin, Developer, DoctorChief, Doctor, NursingSupervisor, or Nurse).
    /// </summary>
    /// <param name="user">The user whose panel permissions are being resolved; must carry authorization data.</param>
    /// <returns>The <see cref="PanelPermissionTypes"/> associated with the matched role.</returns>
    /// <exception cref="ForbbidenException">Thrown when the user has no authorities or no authority grants panel access.</exception>
    public PanelPermissionTypes GetPermissionsForPanel(User user)
    {
        var authorities = user.Authorization;
        if (authorities == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);

        foreach (var auth in authorities)
            if (auth.PanelAuthorization)
            {
                _logger.LogInformation("User {user} has role {role} for panel", user.UserName, auth.Rol);

                switch ((PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType),
                            auth.Rol ?? string.Empty))
                {
                    case PermissionEnum.RolesType.AuthGuest:
                        return _permissionsConfig.Guest.Panel;
                    case PermissionEnum.RolesType.AuthAdmin:
                        return _permissionsConfig.Admin.Panel;
                    case PermissionEnum.RolesType.AuthDeveloper:
                        return _permissionsConfig.Developer.Panel;
                    case PermissionEnum.RolesType.AuthDoctorchief:
                        return _permissionsConfig.DoctorChief.Panel;
                    case PermissionEnum.RolesType.AuthDoctor:
                        return _permissionsConfig.Doctor.Panel;
                    case PermissionEnum.RolesType.AuthNursesupervisor:
                        return _permissionsConfig.NursingSupervisor.Panel;
                    case PermissionEnum.RolesType.AuthNurse:
                        return _permissionsConfig.Nurse.Panel;
                }
            }

        throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
    }

    /// <summary>
    /// Determines whether the specified user has access to a given display, based on the user's role, authorities, and the display record.
    /// Returns false if the display identifier is not a valid ObjectId, or if either the user or the display cannot be found.
    /// The user's authorization set is loaded from the authority repository before evaluating access through role checks against the display.
    /// </summary>
    /// <param name="username">The username used to look up the user attempting to access the display.</param>
    /// <param name="userRole">The role of the user, used in the role-based access evaluation against the display.</param>
    /// <param name="source">The source permission context used when evaluating access to the display.</param>
    /// <param name="displayId">The string identifier of the display; must be parseable as an ObjectId or access is denied.</param>
    /// <returns>A task that resolves to true if the user's role and authorities grant access to the specified display; otherwise, false.</returns>
    public async Task<bool> HasAccessToDisplay(string username, PermissionEnum.RolesType userRole,
            PermissionEnum.SourcePermissionsEnum source, string displayId)
    {
        if (!ObjectId.TryParse(displayId, out var displayObjectId)) return false;

        var display = await displayService.Value.GetById(displayObjectId);
        var user = await userRepository.Value.GetByUserName(username);

        if (user == null || display == null) return false;
        var authorities = await authorityRepository.Value.GetUserAuthorities(user.Id);
        user.Authorization = authorities;

        return SearchRoleInDisplay(display, authorities, userRole);
    }

    /// <summary>
    /// Determines whether the specified user has access to a given unit based on their role and assigned authorities.
    /// Returns false when the user cannot be found by username; otherwise delegates the final check to the role-in-unit search using the retrieved authorities.
    /// </summary>
    /// <param name="username">The username of the user whose access is being verified.</param>
    /// <param name="userRole">The role type to be matched against the user's authorities for the target unit.</param>
    /// <param name="source">The source permission context associated with the access evaluation.</param>
    /// <param name="unitId">The identifier of the unit to check access against.</param>
    /// <returns>A task that resolves to true if the user has the required access to the unit; otherwise, false.</returns>
    public async Task<bool> HasAccessToUnit(string username, PermissionEnum.RolesType userRole,
            PermissionEnum.SourcePermissionsEnum source, string unitId)
    {
        var user = await userRepository.Value.GetByUserName(username);

        if (user == null) return false;
        var authorities = await authorityRepository.Value.GetUserAuthorities(user.Id);
        user.Authorization = authorities;

        return SearchRoleInUnit(unitId, authorities, userRole);
    }

    /// <summary>
    /// Determines whether the specified user has access to a panel by resolving their authorities and checking the required role. Returns false if the user is not found.
    /// </summary>
    /// <param name="username">The username of the user to check.</param>
    /// <param name="userRole">The role required to access the panel.</param>
    /// <param name="source">The source permission context used for the access check.</param>
    /// <returns>true if the user exists and has the required role within the panel; otherwise, false.</returns>
    public async Task<bool> HasAccessToPanel(string username, PermissionEnum.RolesType userRole,
            PermissionEnum.SourcePermissionsEnum source)
    {
        var user = await userRepository.Value.GetByUserName(username);

        if (user == null) return false;
        var authorities = await authorityRepository.Value.GetUserAuthorities(user.Id);
        user.Authorization = authorities;

        return SearchRoleInPanel(authorities, userRole);
    }


    /// <summary>
    /// Determines whether the specified role is present in the authorizations matched by the display's ID, its unit ID, or found via a recursive unit search.
    /// Returns <c>false</c> when the authorities list is null or empty, and parses each authorization's role string into the <see cref="PermissionEnum.RolesType"/> enum for comparison.
    /// </summary>
    /// <param name="display">The display whose identifiers are used to match authorizations.</param>
    /// <param name="authorities">The list of authorizations to search; a null or empty value causes the method to return <c>false</c>.</param>
    /// <param name="role">The role to find within the matched authorizations or the display's unit.</param>
    /// <returns><c>true</c> if the role is found in any matched authorization or in the display's unit; otherwise, <c>false</c>.</returns>
    private static bool SearchRoleInDisplay(Display display, List<Authorization>? authorities,
            PermissionEnum.RolesType role)
    {
        if (authorities is not { Count: > 0 }) return false;

        return (
            from auth in authorities
            let authRole =
                (PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType), auth.Rol ?? string.Empty)
            where display.Id.ToString().Equals(auth.DisplayId) || display.UnitId.ToString().Equals(auth.UnitId)
            let result = SearchRoleInUnit(display.UnitId.ToString(), authorities, role)
            where result || authRole == role
            select authRole
        ).Any();
    }

    /// <summary>
    /// Searches the supplied authorities list for a specific role assigned to the given unit. Returns <c>false</c> when the authorities list is <c>null</c>, or when no matching entry is found.
    /// </summary>
    /// <param name="unitId">The identifier of the unit to match against the authorities entries.</param>
    /// <param name="authorities">The list of authorizations to inspect; if <c>null</c>, the method short-circuits and returns <c>false</c>.</param>
    /// <param name="role">The role type to look for within the authorities of the specified unit.</param>
    /// <returns><c>true</c> if an authority exists for <paramref name="unitId"/> with a role equal to <paramref name="role"/>; otherwise, <c>false</c>.</returns>
    private static bool SearchRoleInUnit(string unitId, List<Authorization>? authorities, PermissionEnum.RolesType role)
    {
        if (authorities == null)
            return false;
        foreach (var auth in authorities)
        {
            var authRole =
                (PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType), auth.Rol ?? string.Empty);
            if (auth.UnitId != null && auth.UnitId.Equals(unitId) && authRole == role) return true;
        }

        return false;
    }

    /// <summary>
    /// Determines whether any authorization entry in the provided list grants the specified role for panel access.
    /// Returns <c>false</c> when the authorities list is <c>null</c>, when an entry has no role mapping, or when no matching role is found with panel authorization enabled.
    /// </summary>
    /// <param name="authorities">The collection of <see cref="Authorization"/> entries to search; may be <c>null</c>.</param>
    /// <param name="role">The role to look for within the panel-authorized entries.</param>
    /// <returns><c>true</c> if at least one entry has <c>PanelAuthorization</c> enabled and matches the specified role; otherwise, <c>false</c>.</returns>
    /// <exception cref="ArgumentException">Thrown when an entry's <c>Rol</c> value cannot be parsed into a valid <see cref="PermissionEnum.RolesType"/>.</exception>
    private static bool SearchRoleInPanel(List<Authorization>? authorities, PermissionEnum.RolesType role)
    {
        if (authorities == null)
            return false;

        return (
            from auth in authorities
            let authRole =
                (PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType), auth.Rol ?? string.Empty)
            where auth.PanelAuthorization && authRole == role
            select auth
        ).Any();
    }
}