const express = require('express');
const request = require('supertest');
const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const { createMethods } = require('@librechat/data-schemas');
const { MongoMemoryServer } = require('mongodb-memory-server');
const {
  SystemRoles,
  AccessRoleIds,
  ResourceType,
  PrincipalType,
} = require('librechat-data-provider');
const { createAgent } = require('~/models');

jest.mock('~/server/services/Files/process', () => ({
  processAgentFileUpload: jest.fn().mockImplementation(async ({ res }) => {
    return res.status(200).json({ message: 'Agent file uploaded', file_id: 'test-file-id' });
  }),
  processImageFile: jest.fn().mockImplementation(async ({ res }) => {
    return res.status(200).json({ message: 'Image processed' });
  }),
  filterFile: jest.fn(),
}));

jest.mock('fs', () => {
  const actualFs = jest.requireActual('fs');
  return {
    ...actualFs,
    promises: {
      ...actualFs.promises,
      unlink: jest.fn().mockResolvedValue(undefined),
    },
  };
});

const fs = require('fs');
const { processAgentFileUpload } = require('~/server/services/Files/process');

const router = require('~/server/routes/files/images');

describe('POST /images - Agent Upload Permission Check (Integration)', () => {
  let mongoServer;
  let authorId;
  let otherUserId;
  let agentCustomId;
  let User;
  let Agent;
  let AclEntry;
  let methods;
  let modelsToCleanup = [];

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

    const { createModels } = require('@librechat/data-schemas');
    const models = createModels(mongoose);
    modelsToCleanup = Object.keys(models);
    Object.assign(mongoose.models, models);
    methods = createMethods(mongoose);

    User = models.User;
    Agent = models.Agent;
    AclEntry = models.AclEntry;

    await methods.seedDefaultRoles();
  });

  afterAll(async () => {
    const collections = mongoose.connection.collections;
    for (const key in collections) {
      await collections[key].deleteMany({});
    }
    for (const modelName of modelsToCleanup) {
      if (mongoose.models[modelName]) {
        delete mongoose.models[modelName];
      }
    }
    await mongoose.disconnect();
    await mongoServer.stop();
  });

  beforeEach(async () => {
    await Agent.deleteMany({});
    await User.deleteMany({});
    await AclEntry.deleteMany({});

    authorId = new mongoose.Types.ObjectId();
    otherUserId = new mongoose.Types.ObjectId();
    agentCustomId = `agent_${uuidv4().replace(/-/g, '').substring(0, 21)}`;

    await User.create({ _id: authorId, username: 'author', email: 'author@test.com' });
    await User.create({ _id: otherUserId, username: 'other', email: 'other@test.com' });

    jest.clearAllMocks();
  });

  const createAppWithUser = (userId, userRole = SystemRoles.USER) => {
    const app = express();
    app.use(express.json());
    app.use((req, _res, next) => {
      if (req.method === 'POST') {
        req.file = {
          originalname: 'test.png',
          mimetype: 'image/png',
          size: 100,
          path: '/tmp/t.png',
          filename: 'test.png',
        };
        req.file_id = uuidv4();
      }
      next();
    });
    app.use((req, _res, next) => {
      req.user = { id: userId.toString(), role: userRole };
      req.app = { locals: {} };
      req.config = { fileStrategy: 'local', paths: { imageOutput: '/tmp/images' } };
      next();
    });
    app.use('/images', router);
    return app;
  };

  it('should return 403 when user has no permission on agent', async () => {
    await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      file_id: uuidv4(),
    });

    expect(response.status).toBe(403);
    expect(response.body.error).toBe('Forbidden');
    expect(processAgentFileUpload).not.toHaveBeenCalled();
    expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png');
  });

  it('should allow upload for agent owner', async () => {
    await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const app = createAppWithUser(authorId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      file_id: uuidv4(),
    });

    expect(response.status).toBe(200);
    expect(processAgentFileUpload).toHaveBeenCalled();
  });

  it('should allow upload for admin regardless of ownership', async () => {
    await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const app = createAppWithUser(otherUserId, SystemRoles.ADMIN);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      file_id: uuidv4(),
    });

    expect(response.status).toBe(200);
    expect(processAgentFileUpload).toHaveBeenCalled();
  });

  it('should allow upload for user with EDIT permission', async () => {
    const agent = await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const { grantPermission } = require('~/server/services/PermissionService');
    await grantPermission({
      principalType: PrincipalType.USER,
      principalId: otherUserId,
      resourceType: ResourceType.AGENT,
      resourceId: agent._id,
      accessRoleId: AccessRoleIds.AGENT_EDITOR,
      grantedBy: authorId,
    });

    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      file_id: uuidv4(),
    });

    expect(response.status).toBe(200);
    expect(processAgentFileUpload).toHaveBeenCalled();
  });

  it('should deny upload for user with only VIEW permission', async () => {
    const agent = await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const { grantPermission } = require('~/server/services/PermissionService');
    await grantPermission({
      principalType: PrincipalType.USER,
      principalId: otherUserId,
      resourceType: ResourceType.AGENT,
      resourceId: agent._id,
      accessRoleId: AccessRoleIds.AGENT_VIEWER,
      grantedBy: authorId,
    });

    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      file_id: uuidv4(),
    });

    expect(response.status).toBe(403);
    expect(response.body.error).toBe('Forbidden');
    expect(processAgentFileUpload).not.toHaveBeenCalled();
    expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png');
  });

  it('should skip permission check for regular image uploads without agent_id/tool_resource', async () => {
    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      file_id: uuidv4(),
    });

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

  it('should return 404 for non-existent agent', async () => {
    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: 'agent_nonexistent123456789',
      tool_resource: 'context',
      file_id: uuidv4(),
    });

    expect(response.status).toBe(404);
    expect(response.body.error).toBe('Not Found');
    expect(processAgentFileUpload).not.toHaveBeenCalled();
    expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png');
  });

  it('should allow message_file attachment (boolean true) without EDIT permission', async () => {
    const agent = await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const { grantPermission } = require('~/server/services/PermissionService');
    await grantPermission({
      principalType: PrincipalType.USER,
      principalId: otherUserId,
      resourceType: ResourceType.AGENT,
      resourceId: agent._id,
      accessRoleId: AccessRoleIds.AGENT_VIEWER,
      grantedBy: authorId,
    });

    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      message_file: true,
      file_id: uuidv4(),
    });

    expect(response.status).toBe(200);
    expect(processAgentFileUpload).toHaveBeenCalled();
  });

  it('should allow message_file attachment (string "true") without EDIT permission', async () => {
    const agent = await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const { grantPermission } = require('~/server/services/PermissionService');
    await grantPermission({
      principalType: PrincipalType.USER,
      principalId: otherUserId,
      resourceType: ResourceType.AGENT,
      resourceId: agent._id,
      accessRoleId: AccessRoleIds.AGENT_VIEWER,
      grantedBy: authorId,
    });

    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      message_file: 'true',
      file_id: uuidv4(),
    });

    expect(response.status).toBe(200);
    expect(processAgentFileUpload).toHaveBeenCalled();
  });

  it('should deny upload when message_file is false (not a message attachment)', async () => {
    const agent = await createAgent({
      id: agentCustomId,
      name: 'Test Agent',
      provider: 'openai',
      model: 'gpt-4',
      author: authorId,
    });

    const { grantPermission } = require('~/server/services/PermissionService');
    await grantPermission({
      principalType: PrincipalType.USER,
      principalId: otherUserId,
      resourceType: ResourceType.AGENT,
      resourceId: agent._id,
      accessRoleId: AccessRoleIds.AGENT_VIEWER,
      grantedBy: authorId,
    });

    const app = createAppWithUser(otherUserId);
    const response = await request(app).post('/images').send({
      endpoint: 'agents',
      agent_id: agentCustomId,
      tool_resource: 'context',
      message_file: false,
      file_id: uuidv4(),
    });

    expect(response.status).toBe(403);
    expect(response.body.error).toBe('Forbidden');
    expect(processAgentFileUpload).not.toHaveBeenCalled();
    expect(fs.promises.unlink).toHaveBeenCalledWith('/tmp/t.png');
  });
});
