const { logger } = require('@librechat/data-schemas');
const { PrincipalType, PermissionTypes, Permissions } = require('librechat-data-provider');
const { checkPeoplePickerAccess } = require('./checkPeoplePickerAccess');
const { getRoleByName } = require('~/models');

jest.mock('~/models');
jest.mock('@librechat/data-schemas', () => ({
  ...jest.requireActual('@librechat/data-schemas'),
  logger: {
    error: jest.fn(),
  },
}));

describe('checkPeoplePickerAccess', () => {
  let req, res, next;

  beforeEach(() => {
    req = {
      user: { id: 'user123', role: 'USER' },
      query: {},
    };
    res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    };
    next = jest.fn();
    jest.clearAllMocks();
  });

  it('should return 401 if user is not authenticated', async () => {
    req.user = null;

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(401);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Unauthorized',
      message: 'Authentication required',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should return 403 if role has no permissions', async () => {
    getRoleByName.mockResolvedValue(null);

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'No permissions configured for user role',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should allow access when searching for users with VIEW_USERS permission', async () => {
    req.query.type = PrincipalType.USER;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(next).toHaveBeenCalled();
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should deny access when searching for users without VIEW_USERS permission', async () => {
    req.query.type = PrincipalType.USER;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: false,
          [Permissions.VIEW_GROUPS]: true,
          [Permissions.VIEW_ROLES]: true,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for users',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should allow access when searching for groups with VIEW_GROUPS permission', async () => {
    req.query.type = PrincipalType.GROUP;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: false,
          [Permissions.VIEW_GROUPS]: true,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(next).toHaveBeenCalled();
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should deny access when searching for groups without VIEW_GROUPS permission', async () => {
    req.query.type = PrincipalType.GROUP;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: true,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for groups',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should allow access when searching for roles with VIEW_ROLES permission', async () => {
    req.query.type = PrincipalType.ROLE;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: false,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: true,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(next).toHaveBeenCalled();
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should deny access when searching for roles without VIEW_ROLES permission', async () => {
    req.query.type = PrincipalType.ROLE;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: true,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for roles',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should deny access when using types param to bypass type-specific check', async () => {
    req.query.types = PrincipalType.GROUP;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for groups',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should deny access when types contains any unpermitted type', async () => {
    req.query.types = `${PrincipalType.USER},${PrincipalType.ROLE}`;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for roles',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should allow access when all requested types are permitted', async () => {
    req.query.types = `${PrincipalType.USER},${PrincipalType.GROUP}`;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: true,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(next).toHaveBeenCalled();
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should validate types when provided as array (Express qs parsing)', async () => {
    req.query.types = [PrincipalType.GROUP, PrincipalType.ROLE];
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: true,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for groups',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should enforce permissions for combined type and types params', async () => {
    req.query.type = PrincipalType.USER;
    req.query.types = PrincipalType.GROUP;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for groups',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should treat all-invalid types values as mixed search', async () => {
    req.query.types = 'foobar';
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(next).toHaveBeenCalled();
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should deny when types is empty string and user has no permissions', async () => {
    req.query.types = '';
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: false,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for users, groups, or roles',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should treat types=public as mixed search since PUBLIC is not a searchable principal type', async () => {
    req.query.types = PrincipalType.PUBLIC;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: true,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(next).toHaveBeenCalled();
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should allow mixed search when user has at least one permission', async () => {
    // No type specified = mixed search
    req.query.type = undefined;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: false,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: true,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(next).toHaveBeenCalled();
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should deny mixed search when user has no permissions', async () => {
    // No type specified = mixed search
    req.query.type = undefined;
    getRoleByName.mockResolvedValue({
      permissions: {
        [PermissionTypes.PEOPLE_PICKER]: {
          [Permissions.VIEW_USERS]: false,
          [Permissions.VIEW_GROUPS]: false,
          [Permissions.VIEW_ROLES]: false,
        },
      },
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for users, groups, or roles',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should handle errors gracefully', async () => {
    const error = new Error('Database error');
    getRoleByName.mockRejectedValue(error);

    await checkPeoplePickerAccess(req, res, next);

    expect(logger.error).toHaveBeenCalledWith(
      '[checkPeoplePickerAccess][user123] error for type=undefined, types=undefined',
      error,
    );
    expect(res.status).toHaveBeenCalledWith(500);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Internal Server Error',
      message: 'Failed to check permissions',
    });
    expect(next).not.toHaveBeenCalled();
  });

  it('should handle missing permissions object gracefully', async () => {
    req.query.type = PrincipalType.USER;
    getRoleByName.mockResolvedValue({
      permissions: {}, // No PEOPLE_PICKER permissions
    });

    await checkPeoplePickerAccess(req, res, next);

    expect(res.status).toHaveBeenCalledWith(403);
    expect(res.json).toHaveBeenCalledWith({
      error: 'Forbidden',
      message: 'Insufficient permissions to search for users',
    });
    expect(next).not.toHaveBeenCalled();
  });
});
