jest.mock('~/models', () => ({
  getConvo: jest.fn(),
}));

jest.mock('@librechat/api', () => ({
  GenerationJobManager: {
    getJob: jest.fn(),
  },
}));

jest.mock('@librechat/data-schemas', () => ({
  logger: {
    warn: jest.fn(),
  },
}));

const validateMessageReq = require('../validateMessageReq');
const { getConvo } = require('~/models');
const { GenerationJobManager } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');

function createResponse() {
  const res = {
    json: jest.fn(),
    send: jest.fn(),
    status: jest.fn(),
  };
  res.status.mockReturnValue(res);
  return res;
}

describe('validateMessageReq', () => {
  const userId = 'user-123';

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('should reject requests when URL and body conversationId values differ', async () => {
    const req = {
      params: { conversationId: 'convo-owned' },
      body: { conversationId: 'convo-victim' },
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();

    await validateMessageReq(req, res, next);

    expect(res.status).toHaveBeenCalledWith(400);
    expect(res.json).toHaveBeenCalledWith({ error: 'Conversation ID mismatch' });
    expect(getConvo).not.toHaveBeenCalled();
    expect(next).not.toHaveBeenCalled();
  });

  it('should reject requests when URL and nested message conversationId values differ', async () => {
    const req = {
      params: { conversationId: 'convo-owned' },
      body: { message: { conversationId: 'convo-victim' } },
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();

    await validateMessageReq(req, res, next);

    expect(res.status).toHaveBeenCalledWith(400);
    expect(res.json).toHaveBeenCalledWith({ error: 'Conversation ID mismatch' });
    expect(getConvo).not.toHaveBeenCalled();
    expect(next).not.toHaveBeenCalled();
  });

  it('should validate ownership against the URL conversationId when values match', async () => {
    const req = {
      params: { conversationId: 'convo-owned' },
      body: { conversationId: 'convo-owned' },
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();
    getConvo.mockResolvedValue({ conversationId: 'convo-owned', user: userId });

    await validateMessageReq(req, res, next);

    expect(getConvo).toHaveBeenCalledWith(userId, 'convo-owned');
    expect(next).toHaveBeenCalledTimes(1);
  });

  it('should allow message reads for an owned active generation job before the conversation is saved', async () => {
    const req = {
      method: 'GET',
      params: { conversationId: 'active-convo' },
      body: {},
      user: { id: userId, tenantId: 'tenant-a' },
    };
    const res = createResponse();
    const next = jest.fn();
    getConvo.mockResolvedValue(null);
    GenerationJobManager.getJob.mockResolvedValue({
      status: 'running',
      metadata: { userId, tenantId: 'tenant-a' },
    });

    await validateMessageReq(req, res, next);

    expect(GenerationJobManager.getJob).toHaveBeenCalledWith('active-convo');
    expect(next).toHaveBeenCalledTimes(1);
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should allow message reads for an owned active generation job without tenant metadata', async () => {
    const req = {
      method: 'GET',
      params: { conversationId: 'active-convo' },
      body: {},
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();
    getConvo.mockResolvedValue(null);
    GenerationJobManager.getJob.mockResolvedValue({
      status: 'running',
      metadata: { userId },
    });

    await validateMessageReq(req, res, next);

    expect(next).toHaveBeenCalledTimes(1);
    expect(res.status).not.toHaveBeenCalled();
  });

  it('should reject active job message reads owned by another user', async () => {
    const req = {
      method: 'GET',
      params: { conversationId: 'active-convo' },
      body: {},
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();
    getConvo.mockResolvedValue(null);
    GenerationJobManager.getJob.mockResolvedValue({
      status: 'running',
      metadata: { userId: 'another-user' },
    });

    await validateMessageReq(req, res, next);

    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({ error: 'Conversation not found' });
    expect(next).not.toHaveBeenCalled();
  });

  it('should reject active job message reads from another tenant', async () => {
    const req = {
      method: 'GET',
      params: { conversationId: 'active-convo' },
      body: {},
      user: { id: userId, tenantId: 'tenant-a' },
    };
    const res = createResponse();
    const next = jest.fn();
    getConvo.mockResolvedValue(null);
    GenerationJobManager.getJob.mockResolvedValue({
      status: 'running',
      metadata: { userId, tenantId: 'tenant-b' },
    });

    await validateMessageReq(req, res, next);

    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({ error: 'Conversation not found' });
    expect(next).not.toHaveBeenCalled();
  });

  it('should reject message-by-id reads before the conversation is saved', async () => {
    const req = {
      method: 'GET',
      params: { conversationId: 'active-convo', messageId: 'message-id' },
      body: {},
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();
    getConvo.mockResolvedValue(null);

    await validateMessageReq(req, res, next);

    expect(GenerationJobManager.getJob).not.toHaveBeenCalled();
    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({ error: 'Conversation not found' });
    expect(next).not.toHaveBeenCalled();
  });

  it('should return not found when active job lookup fails', async () => {
    const req = {
      method: 'GET',
      params: { conversationId: 'active-convo' },
      body: {},
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();
    const error = new Error('job store unavailable');
    getConvo.mockResolvedValue(null);
    GenerationJobManager.getJob.mockRejectedValue(error);

    await validateMessageReq(req, res, next);

    expect(GenerationJobManager.getJob).toHaveBeenCalledWith('active-convo');
    expect(logger.warn).toHaveBeenCalledWith(
      '[validateMessageReq] Active job lookup failed for active-convo:',
      error,
    );
    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({ error: 'Conversation not found' });
    expect(next).not.toHaveBeenCalled();
  });

  it('should not allow unsaved conversation writes through active job ownership', async () => {
    const req = {
      method: 'POST',
      params: { conversationId: 'active-convo' },
      body: {},
      user: { id: userId },
    };
    const res = createResponse();
    const next = jest.fn();
    getConvo.mockResolvedValue(null);

    await validateMessageReq(req, res, next);

    expect(GenerationJobManager.getJob).not.toHaveBeenCalled();
    expect(res.status).toHaveBeenCalledWith(404);
    expect(next).not.toHaveBeenCalled();
  });
});
