const mongoose = require('mongoose');
const { ObjectId } = require('mongodb');
const { logger } = require('@librechat/data-schemas');
const { MongoMemoryServer } = require('mongodb-memory-server');
const {
  Constants,
  ResourceType,
  AccessRoleIds,
  PrincipalType,
  PrincipalModel,
  PermissionBits,
} = require('librechat-data-provider');

// Mock the config/connect module to prevent connection attempts during tests
jest.mock('../../config/connect', () => jest.fn().mockResolvedValue(true));

// Disable console for tests
logger.silent = true;

describe('PromptGroup Migration Script', () => {
  let mongoServer;
  let Prompt, PromptGroup, AclEntry, AccessRole, User, Project;
  let migrateToPromptGroupPermissions;
  let testOwner, testProject;
  let ownerRole, viewerRole;

  beforeAll(async () => {
    // Set up MongoDB memory server
    mongoServer = await MongoMemoryServer.create();
    const mongoUri = mongoServer.getUri();
    await mongoose.connect(mongoUri);

    // Initialize models
    const dbModels = require('~/db/models');
    Prompt = dbModels.Prompt;
    PromptGroup = dbModels.PromptGroup;
    AclEntry = dbModels.AclEntry;
    AccessRole = dbModels.AccessRole;
    User = dbModels.User;
    Project = dbModels.Project;

    // Create test user
    testOwner = await User.create({
      name: 'Test Owner',
      email: 'owner@test.com',
      role: 'USER',
    });

    // Create test project with the proper name
    const projectName = Constants.GLOBAL_PROJECT_NAME || 'instance';
    testProject = await Project.create({
      name: projectName,
      description: 'Global project',
      promptGroupIds: [],
    });

    // Create promptGroup access roles
    ownerRole = await AccessRole.create({
      accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
      name: 'Owner',
      description: 'Full control over promptGroups',
      resourceType: ResourceType.PROMPTGROUP,
      permBits:
        PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE,
    });

    viewerRole = await AccessRole.create({
      accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
      name: 'Viewer',
      description: 'Can view promptGroups',
      resourceType: ResourceType.PROMPTGROUP,
      permBits: PermissionBits.VIEW,
    });

    await AccessRole.create({
      accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
      name: 'Editor',
      description: 'Can view and edit promptGroups',
      resourceType: ResourceType.PROMPTGROUP,
      permBits: PermissionBits.VIEW | PermissionBits.EDIT,
    });

    // Import migration function
    const migration = require('../../config/migrate-prompt-permissions');
    migrateToPromptGroupPermissions = migration.migrateToPromptGroupPermissions;
  });

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

  beforeEach(async () => {
    // Clean up before each test
    await Prompt.deleteMany({});
    await PromptGroup.deleteMany({});
    await AclEntry.deleteMany({});
    // Reset the project's promptGroupIds array
    testProject.promptGroupIds = [];
    await testProject.save();
  });

  it('should categorize promptGroups correctly in dry run', async () => {
    // Create global prompt group (in Global project)
    const globalPromptGroup = await PromptGroup.create({
      name: 'Global Group',
      author: testOwner._id,
      authorName: testOwner.name,
      productionId: new ObjectId(),
    });

    // Create private prompt group (not in any project)
    await PromptGroup.create({
      name: 'Private Group',
      author: testOwner._id,
      authorName: testOwner.name,
      productionId: new ObjectId(),
    });

    // Add global group to project's promptGroupIds array
    testProject.promptGroupIds = [globalPromptGroup._id];
    await testProject.save();

    const result = await migrateToPromptGroupPermissions({ dryRun: true });

    expect(result.dryRun).toBe(true);
    expect(result.summary.total).toBe(2);
    expect(result.summary.globalViewAccess).toBe(1);
    expect(result.summary.privateGroups).toBe(1);
  });

  it('should grant appropriate permissions during migration', async () => {
    // Create prompt groups
    const globalPromptGroup = await PromptGroup.create({
      name: 'Global Group',
      author: testOwner._id,
      authorName: testOwner.name,
      productionId: new ObjectId(),
    });

    const privatePromptGroup = await PromptGroup.create({
      name: 'Private Group',
      author: testOwner._id,
      authorName: testOwner.name,
      productionId: new ObjectId(),
    });

    // Add global group to project's promptGroupIds array
    testProject.promptGroupIds = [globalPromptGroup._id];
    await testProject.save();

    const result = await migrateToPromptGroupPermissions({ dryRun: false });

    expect(result.migrated).toBe(2);
    expect(result.errors).toBe(0);
    expect(result.ownerGrants).toBe(2);
    expect(result.publicViewGrants).toBe(1);

    // Check global promptGroup permissions
    const globalOwnerEntry = await AclEntry.findOne({
      resourceType: ResourceType.PROMPTGROUP,
      resourceId: globalPromptGroup._id,
      principalType: PrincipalType.USER,
      principalId: testOwner._id,
    });
    expect(globalOwnerEntry).toBeTruthy();
    expect(globalOwnerEntry.permBits).toBe(ownerRole.permBits);

    const globalPublicEntry = await AclEntry.findOne({
      resourceType: ResourceType.PROMPTGROUP,
      resourceId: globalPromptGroup._id,
      principalType: PrincipalType.PUBLIC,
    });
    expect(globalPublicEntry).toBeTruthy();
    expect(globalPublicEntry.permBits).toBe(viewerRole.permBits);

    // Check private promptGroup permissions
    const privateOwnerEntry = await AclEntry.findOne({
      resourceType: ResourceType.PROMPTGROUP,
      resourceId: privatePromptGroup._id,
      principalType: PrincipalType.USER,
      principalId: testOwner._id,
    });
    expect(privateOwnerEntry).toBeTruthy();
    expect(privateOwnerEntry.permBits).toBe(ownerRole.permBits);

    const privatePublicEntry = await AclEntry.findOne({
      resourceType: ResourceType.PROMPTGROUP,
      resourceId: privatePromptGroup._id,
      principalType: PrincipalType.PUBLIC,
    });
    expect(privatePublicEntry).toBeNull();
  });

  it('should skip promptGroups that already have ACL entries', async () => {
    // Create prompt groups
    const promptGroup1 = await PromptGroup.create({
      name: 'Group 1',
      author: testOwner._id,
      authorName: testOwner.name,
      productionId: new ObjectId(),
    });

    const promptGroup2 = await PromptGroup.create({
      name: 'Group 2',
      author: testOwner._id,
      authorName: testOwner.name,
      productionId: new ObjectId(),
    });

    // Grant permission to one promptGroup manually (simulating it already has ACL)
    await AclEntry.create({
      principalType: PrincipalType.USER,
      principalId: testOwner._id,
      principalModel: PrincipalModel.USER,
      resourceType: ResourceType.PROMPTGROUP,
      resourceId: promptGroup1._id,
      permBits: ownerRole.permBits,
      roleId: ownerRole._id,
      grantedBy: testOwner._id,
      grantedAt: new Date(),
    });

    const result = await migrateToPromptGroupPermissions({ dryRun: false });

    // Should only migrate promptGroup2, skip promptGroup1
    expect(result.migrated).toBe(1);
    expect(result.errors).toBe(0);

    // Verify promptGroup2 now has permissions
    const group2Entry = await AclEntry.findOne({
      resourceType: ResourceType.PROMPTGROUP,
      resourceId: promptGroup2._id,
    });
    expect(group2Entry).toBeTruthy();
  });

  it('should handle promptGroups with prompts correctly', async () => {
    // Create a promptGroup with some prompts
    const promptGroup = await PromptGroup.create({
      name: 'Group with Prompts',
      author: testOwner._id,
      authorName: testOwner.name,
      productionId: new ObjectId(),
    });

    // Create some prompts in this group
    await Prompt.create({
      prompt: 'First prompt',
      author: testOwner._id,
      groupId: promptGroup._id,
      type: 'text',
    });

    await Prompt.create({
      prompt: 'Second prompt',
      author: testOwner._id,
      groupId: promptGroup._id,
      type: 'text',
    });

    const result = await migrateToPromptGroupPermissions({ dryRun: false });

    expect(result.migrated).toBe(1);
    expect(result.errors).toBe(0);

    // Verify the promptGroup has permissions
    const groupEntry = await AclEntry.findOne({
      resourceType: ResourceType.PROMPTGROUP,
      resourceId: promptGroup._id,
    });
    expect(groupEntry).toBeTruthy();

    // Verify no prompt-level permissions were created
    const promptEntries = await AclEntry.find({
      resourceType: 'prompt',
    });
    expect(promptEntries).toHaveLength(0);
  });
});
