const express = require('express');
const request = require('supertest');
const { SystemRoles, roleDefaults } = require('librechat-data-provider');

const mockGetRoleByName = jest.fn();
const mockHasCapability = jest.fn();

jest.mock('~/server/middleware', () => ({
  requireJwtAuth: (_req, _res, next) => next(),
}));

jest.mock('~/server/middleware/roles/capabilities', () => ({
  hasCapability: (...args) => mockHasCapability(...args),
  requireCapability: () => (_req, _res, next) => next(),
}));

jest.mock('~/models', () => ({
  getRoleByName: (...args) => mockGetRoleByName(...args),
  updateRoleByName: jest.fn(),
}));

const rolesRouter = require('../roles');

function createApp(user) {
  const app = express();
  app.use(express.json());
  app.use((req, _res, next) => {
    req.user = user;
    next();
  });
  app.use('/api/roles', rolesRouter);
  return app;
}

const staffRole = {
  name: 'STAFF',
  permissions: {
    PROMPTS: { USE: true, CREATE: false },
  },
};

const userRole = roleDefaults[SystemRoles.USER];
const adminRole = roleDefaults[SystemRoles.ADMIN];

beforeEach(() => {
  jest.clearAllMocks();
  mockHasCapability.mockResolvedValue(false);
  mockGetRoleByName.mockResolvedValue(null);
});

describe('GET /api/roles/:roleName — isOwnRole authorization', () => {
  it('allows a custom role user to fetch their own role', async () => {
    mockGetRoleByName.mockResolvedValue(staffRole);
    const app = createApp({ id: 'u1', role: 'STAFF' });

    const res = await request(app).get('/api/roles/STAFF');

    expect(res.status).toBe(200);
    expect(res.body.name).toBe('STAFF');
    expect(mockGetRoleByName).toHaveBeenCalledWith('STAFF', '-_id -__v');
  });

  it('returns 403 when a custom role user requests a different custom role', async () => {
    const app = createApp({ id: 'u1', role: 'STAFF' });

    const res = await request(app).get('/api/roles/MANAGER');

    expect(res.status).toBe(403);
    expect(mockGetRoleByName).not.toHaveBeenCalled();
  });

  it('returns 403 when a custom role user requests ADMIN', async () => {
    const app = createApp({ id: 'u1', role: 'STAFF' });

    const res = await request(app).get('/api/roles/ADMIN');

    expect(res.status).toBe(403);
    expect(mockGetRoleByName).not.toHaveBeenCalled();
  });

  it('allows USER to fetch the USER role (roleDefaults key)', async () => {
    mockGetRoleByName.mockResolvedValue(userRole);
    const app = createApp({ id: 'u1', role: SystemRoles.USER });

    const res = await request(app).get(`/api/roles/${SystemRoles.USER}`);

    expect(res.status).toBe(200);
  });

  it('returns 403 when USER requests the ADMIN role', async () => {
    const app = createApp({ id: 'u1', role: SystemRoles.USER });

    const res = await request(app).get(`/api/roles/${SystemRoles.ADMIN}`);

    expect(res.status).toBe(403);
  });

  it('allows ADMIN user to fetch their own ADMIN role via isOwnRole', async () => {
    mockHasCapability.mockResolvedValue(false);
    mockGetRoleByName.mockResolvedValue(adminRole);
    const app = createApp({ id: 'u1', role: SystemRoles.ADMIN });

    const res = await request(app).get(`/api/roles/${SystemRoles.ADMIN}`);

    expect(res.status).toBe(200);
  });

  it('allows any user with READ_ROLES capability to fetch any role', async () => {
    mockHasCapability.mockResolvedValue(true);
    mockGetRoleByName.mockResolvedValue(staffRole);
    const app = createApp({ id: 'u1', role: SystemRoles.USER });

    const res = await request(app).get('/api/roles/STAFF');

    expect(res.status).toBe(200);
    expect(res.body.name).toBe('STAFF');
  });

  it('returns 404 when the requested role does not exist', async () => {
    mockGetRoleByName.mockResolvedValue(null);
    const app = createApp({ id: 'u1', role: 'GHOST' });

    const res = await request(app).get('/api/roles/GHOST');

    expect(res.status).toBe(404);
  });

  it('returns 500 when getRoleByName throws', async () => {
    mockGetRoleByName.mockRejectedValue(new Error('db error'));
    const app = createApp({ id: 'u1', role: SystemRoles.USER });

    const res = await request(app).get(`/api/roles/${SystemRoles.USER}`);

    expect(res.status).toBe(500);
  });

  it('returns 403 for prototype property names like constructor (no prototype pollution)', async () => {
    const app = createApp({ id: 'u1', role: 'STAFF' });

    const res = await request(app).get('/api/roles/constructor');

    expect(res.status).toBe(403);
    expect(mockGetRoleByName).not.toHaveBeenCalled();
  });

  it('treats hasCapability failure as no capability (does not 500)', async () => {
    mockHasCapability.mockRejectedValue(new Error('capability check failed'));
    const app = createApp({ id: 'u1', role: 'STAFF' });
    mockGetRoleByName.mockResolvedValue(staffRole);

    const res = await request(app).get('/api/roles/STAFF');

    expect(res.status).toBe(200);
  });
});
