const express = require('express');
const request = require('supertest');
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
const { createModels, createMethods } = require('@librechat/data-schemas');
const { PrincipalType, SystemRoles } = require('librechat-data-provider');

/**
 * Integration test for the admin grants routes.
 *
 * Validates the full Express wiring: route registration → middleware →
 * handler → real MongoDB. Auth middleware is injected (matching the repo
 * pattern in keys.spec.js) so we can control the caller identity without
 * a real JWT, while the handler DI deps use real DB methods.
 */

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

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

let mongoServer;
let db;

beforeAll(async () => {
  mongoServer = await MongoMemoryServer.create();
  await mongoose.connect(mongoServer.getUri());
  createModels(mongoose);
  db = createMethods(mongoose);
  await db.seedSystemGrants();
  await db.initializeRoles();
  await db.seedDefaultRoles();
});

afterAll(async () => {
  await mongoose.disconnect();
  await mongoServer.stop();
});

afterEach(async () => {
  const SystemGrant = mongoose.models.SystemGrant;
  // Clean non-seed grants (keep admin seed)
  await SystemGrant.deleteMany({
    $or: [
      { principalId: { $ne: SystemRoles.ADMIN } },
      { principalType: { $ne: PrincipalType.ROLE } },
    ],
  });
});

function createApp(user) {
  const { createAdminGrantsHandlers, getCachedPrincipals } = require('@librechat/api');

  const handlers = createAdminGrantsHandlers({
    listGrants: db.listGrants,
    countGrants: db.countGrants,
    getCapabilitiesForPrincipal: db.getCapabilitiesForPrincipal,
    getCapabilitiesForPrincipals: db.getCapabilitiesForPrincipals,
    grantCapability: db.grantCapability,
    revokeCapability: db.revokeCapability,
    getUserPrincipals: db.getUserPrincipals,
    hasCapabilityForPrincipals: db.hasCapabilityForPrincipals,
    getHeldCapabilities: db.getHeldCapabilities,
    getCachedPrincipals,
    checkRoleExists: async (name) => (await db.getRoleByName(name)) != null,
  });

  const app = express();
  app.use(express.json());
  app.use((req, _res, next) => {
    req.user = user;
    next();
  });

  const router = express.Router();
  router.get('/', handlers.listGrants);
  router.get('/effective', handlers.getEffectiveCapabilities);
  router.get('/:principalType/:principalId', handlers.getPrincipalGrants);
  router.post('/', handlers.assignGrant);
  router.delete('/:principalType/:principalId/:capability', handlers.revokeGrant);
  app.use('/api/admin/grants', router);

  return app;
}

describe('Admin Grants Routes — Integration', () => {
  const adminUserId = new mongoose.Types.ObjectId();
  const adminUser = {
    _id: adminUserId,
    id: adminUserId.toString(),
    role: SystemRoles.ADMIN,
  };

  it('GET / returns seeded admin grants', async () => {
    const app = createApp(adminUser);
    const res = await request(app).get('/api/admin/grants').expect(200);

    expect(res.body).toHaveProperty('grants');
    expect(res.body).toHaveProperty('total');
    expect(res.body.grants.length).toBeGreaterThan(0);
    // Seeded grants are for the ADMIN role
    expect(res.body.grants[0].principalType).toBe(PrincipalType.ROLE);
  });

  it('GET /effective returns capabilities for admin', async () => {
    const app = createApp(adminUser);
    const res = await request(app).get('/api/admin/grants/effective').expect(200);

    expect(res.body).toHaveProperty('capabilities');
    expect(res.body.capabilities).toContain('access:admin');
    expect(res.body.capabilities).toContain('manage:roles');
  });

  it('POST / assigns a grant and DELETE / revokes it', async () => {
    const app = createApp(adminUser);

    // Assign
    const assignRes = await request(app)
      .post('/api/admin/grants')
      .send({
        principalType: PrincipalType.ROLE,
        principalId: SystemRoles.USER,
        capability: 'read:users',
      })
      .expect(201);

    expect(assignRes.body.grant).toMatchObject({
      principalType: PrincipalType.ROLE,
      principalId: SystemRoles.USER,
      capability: 'read:users',
    });

    // Verify via GET
    const getRes = await request(app)
      .get(`/api/admin/grants/${PrincipalType.ROLE}/${SystemRoles.USER}`)
      .expect(200);

    expect(getRes.body.grants.some((g) => g.capability === 'read:users')).toBe(true);

    // Revoke
    await request(app)
      .delete(`/api/admin/grants/${PrincipalType.ROLE}/${SystemRoles.USER}/read:users`)
      .expect(200);

    // Verify revoked
    const afterRes = await request(app)
      .get(`/api/admin/grants/${PrincipalType.ROLE}/${SystemRoles.USER}`)
      .expect(200);

    expect(afterRes.body.grants.some((g) => g.capability === 'read:users')).toBe(false);
  });

  it('POST / returns 400 for non-existent role when checkRoleExists is wired', async () => {
    const app = createApp(adminUser);

    const res = await request(app)
      .post('/api/admin/grants')
      .send({
        principalType: PrincipalType.ROLE,
        principalId: 'nonexistent-role',
        capability: 'read:users',
      })
      .expect(400);

    expect(res.body.error).toBe('Role not found');
  });

  it('POST / returns 401 without authenticated user', async () => {
    const app = createApp(undefined);

    const res = await request(app)
      .post('/api/admin/grants')
      .send({
        principalType: PrincipalType.ROLE,
        principalId: SystemRoles.USER,
        capability: 'read:users',
      })
      .expect(401);

    expect(res.body).toHaveProperty('error', 'Authentication required');
  });
});
