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

jest.mock('@librechat/data-schemas', () => ({
  ...jest.requireActual('@librechat/data-schemas'),
  getTransactionSupport: jest.fn().mockResolvedValue(false),
  createModels: jest.requireActual('@librechat/data-schemas').createModels,
  createMethods: jest.requireActual('@librechat/data-schemas').createMethods,
}));

jest.mock('~/server/services/GraphApiService', () => ({
  entraIdPrincipalFeatureEnabled: jest.fn().mockReturnValue(false),
  getUserOwnedEntraGroups: jest.fn().mockResolvedValue([]),
  getUserEntraGroups: jest.fn().mockResolvedValue([]),
  getGroupMembers: jest.fn().mockResolvedValue([]),
  getGroupOwners: jest.fn().mockResolvedValue([]),
}));

jest.mock('~/config', () => ({
  logger: { error: jest.fn() },
}));

let mongoServer;
let methods;
let SystemGrant;

beforeAll(async () => {
  mongoServer = await MongoMemoryServer.create();
  await mongoose.connect(mongoServer.getUri());

  createModels(mongoose);
  const dbModels = require('~/db/models');
  Object.assign(mongoose.models, dbModels);
  SystemGrant = dbModels.SystemGrant;

  methods = createMethods(mongoose, {
    matchModelName: () => null,
    findMatchingPattern: () => null,
    getCache: () => ({
      get: async () => null,
      set: async () => {},
    }),
  });
});

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

beforeEach(async () => {
  await SystemGrant.deleteMany({});
});

describe('SystemGrant methods', () => {
  describe('seedSystemGrants', () => {
    it('seeds all capabilities for the ADMIN role', async () => {
      await methods.seedSystemGrants();

      const grants = await SystemGrant.find({
        principalType: PrincipalType.ROLE,
        principalId: SystemRoles.ADMIN,
      }).lean();

      const expectedCount = Object.values(SystemCapabilities).length;
      expect(grants).toHaveLength(expectedCount);

      const capabilities = grants.map((g) => g.capability).sort();
      const expected = Object.values(SystemCapabilities).sort();
      expect(capabilities).toEqual(expected);
    });

    it('is idempotent — calling twice does not duplicate grants', async () => {
      await methods.seedSystemGrants();
      await methods.seedSystemGrants();

      const count = await SystemGrant.countDocuments({
        principalType: PrincipalType.ROLE,
        principalId: SystemRoles.ADMIN,
      });

      expect(count).toBe(Object.values(SystemCapabilities).length);
    });

    it('seeds grants with no tenantId', async () => {
      await methods.seedSystemGrants();

      const withTenant = await SystemGrant.countDocuments({
        principalType: PrincipalType.ROLE,
        principalId: SystemRoles.ADMIN,
        tenantId: { $exists: true },
      });

      expect(withTenant).toBe(0);
    });
  });

  describe('grantCapability / revokeCapability', () => {
    it('grants a capability to a user', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      });

      const grant = await SystemGrant.findOne({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      }).lean();

      expect(grant).toBeTruthy();
      expect(grant.grantedAt).toBeInstanceOf(Date);
    });

    it('upsert does not create duplicates', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      });

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      });

      const count = await SystemGrant.countDocuments({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      });

      expect(count).toBe(1);
    });

    it('revokes a capability', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      });

      await methods.revokeCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      });

      const grant = await SystemGrant.findOne({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_USERS,
      }).lean();

      expect(grant).toBeNull();
    });
  });

  describe('hasCapabilityForPrincipals', () => {
    it('returns true when role principal has the capability', async () => {
      await methods.seedSystemGrants();

      const principals = [
        { principalType: PrincipalType.USER, principalId: new mongoose.Types.ObjectId() },
        { principalType: PrincipalType.ROLE, principalId: SystemRoles.ADMIN },
        { principalType: PrincipalType.PUBLIC },
      ];

      const result = await methods.hasCapabilityForPrincipals({
        principals,
        capability: SystemCapabilities.ACCESS_ADMIN,
      });

      expect(result).toBe(true);
    });

    it('returns false when no principal has the capability', async () => {
      const principals = [
        { principalType: PrincipalType.USER, principalId: new mongoose.Types.ObjectId() },
        { principalType: PrincipalType.ROLE, principalId: SystemRoles.USER },
        { principalType: PrincipalType.PUBLIC },
      ];

      const result = await methods.hasCapabilityForPrincipals({
        principals,
        capability: SystemCapabilities.ACCESS_ADMIN,
      });

      expect(result).toBe(false);
    });

    it('returns false for an empty principals list', async () => {
      const result = await methods.hasCapabilityForPrincipals({
        principals: [],
        capability: SystemCapabilities.ACCESS_ADMIN,
      });

      expect(result).toBe(false);
    });

    it('ignores PUBLIC principals', async () => {
      const result = await methods.hasCapabilityForPrincipals({
        principals: [{ principalType: PrincipalType.PUBLIC }],
        capability: SystemCapabilities.ACCESS_ADMIN,
      });

      expect(result).toBe(false);
    });

    it('matches user-level grants', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_CONFIGS,
      });

      const principals = [
        { principalType: PrincipalType.USER, principalId: userId },
        { principalType: PrincipalType.ROLE, principalId: SystemRoles.USER },
        { principalType: PrincipalType.PUBLIC },
      ];

      const result = await methods.hasCapabilityForPrincipals({
        principals,
        capability: SystemCapabilities.READ_CONFIGS,
      });

      expect(result).toBe(true);
    });

    it('matches group-level grants', async () => {
      const groupId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.GROUP,
        principalId: groupId,
        capability: SystemCapabilities.READ_USAGE,
      });

      const principals = [
        { principalType: PrincipalType.USER, principalId: new mongoose.Types.ObjectId() },
        { principalType: PrincipalType.GROUP, principalId: groupId },
        { principalType: PrincipalType.PUBLIC },
      ];

      const result = await methods.hasCapabilityForPrincipals({
        principals,
        capability: SystemCapabilities.READ_USAGE,
      });

      expect(result).toBe(true);
    });
  });

  describe('getCapabilitiesForPrincipal', () => {
    it('lists all capabilities for a principal', async () => {
      await methods.seedSystemGrants();

      const grants = await methods.getCapabilitiesForPrincipal({
        principalType: PrincipalType.ROLE,
        principalId: SystemRoles.ADMIN,
      });

      expect(grants).toHaveLength(Object.values(SystemCapabilities).length);
    });

    it('returns empty array for a principal with no grants', async () => {
      const grants = await methods.getCapabilitiesForPrincipal({
        principalType: PrincipalType.ROLE,
        principalId: SystemRoles.USER,
      });

      expect(grants).toHaveLength(0);
    });
  });

  describe('principalId normalization', () => {
    it('grant with string userId is found by hasCapabilityForPrincipals with ObjectId', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId.toString(), // string input
        capability: SystemCapabilities.READ_USAGE,
      });

      const result = await methods.hasCapabilityForPrincipals({
        principals: [{ principalType: PrincipalType.USER, principalId: userId }], // ObjectId input
        capability: SystemCapabilities.READ_USAGE,
      });

      expect(result).toBe(true);
    });

    it('revoke with string userId removes the grant stored as ObjectId', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId.toString(),
        capability: SystemCapabilities.READ_USAGE,
      });

      await methods.revokeCapability({
        principalType: PrincipalType.USER,
        principalId: userId.toString(), // string revoke
        capability: SystemCapabilities.READ_USAGE,
      });

      const result = await methods.hasCapabilityForPrincipals({
        principals: [{ principalType: PrincipalType.USER, principalId: userId }],
        capability: SystemCapabilities.READ_USAGE,
      });

      expect(result).toBe(false);
    });

    it('getCapabilitiesForPrincipal with string userId returns grants stored as ObjectId', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId.toString(),
        capability: SystemCapabilities.READ_USAGE,
      });

      const grants = await methods.getCapabilitiesForPrincipal({
        principalType: PrincipalType.USER,
        principalId: userId.toString(), // string lookup
      });

      expect(grants).toHaveLength(1);
      expect(grants[0].capability).toBe(SystemCapabilities.READ_USAGE);
    });
  });

  describe('tenant scoping', () => {
    it('tenant-scoped grant does not match platform-level query', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_CONFIGS,
        tenantId: 'tenant-1',
      });

      const result = await methods.hasCapabilityForPrincipals({
        principals: [{ principalType: PrincipalType.USER, principalId: userId }],
        capability: SystemCapabilities.READ_CONFIGS,
      });

      expect(result).toBe(false);
    });

    it('tenant-scoped grant matches same-tenant query', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_CONFIGS,
        tenantId: 'tenant-1',
      });

      const result = await methods.hasCapabilityForPrincipals({
        principals: [{ principalType: PrincipalType.USER, principalId: userId }],
        capability: SystemCapabilities.READ_CONFIGS,
        tenantId: 'tenant-1',
      });

      expect(result).toBe(true);
    });

    it('tenant-scoped grant does not match different tenant', async () => {
      const userId = new mongoose.Types.ObjectId();

      await methods.grantCapability({
        principalType: PrincipalType.USER,
        principalId: userId,
        capability: SystemCapabilities.READ_CONFIGS,
        tenantId: 'tenant-1',
      });

      const result = await methods.hasCapabilityForPrincipals({
        principals: [{ principalType: PrincipalType.USER, principalId: userId }],
        capability: SystemCapabilities.READ_CONFIGS,
        tenantId: 'tenant-2',
      });

      expect(result).toBe(false);
    });
  });
});
