const express = require('express');
const request = require('supertest');

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

jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next());

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

describe('Keys Routes', () => {
  let app;
  const { updateUserKey, deleteUserKey, getUserKeyExpiry } = require('~/models');

  beforeAll(() => {
    const keysRouter = require('../keys');

    app = express();
    app.use(express.json());

    app.use((req, res, next) => {
      req.user = { id: 'test-user-123' };
      next();
    });

    app.use('/api/keys', keysRouter);
  });

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

  describe('PUT /', () => {
    it('should update a user key with the authenticated user ID', async () => {
      updateUserKey.mockResolvedValue({});

      const response = await request(app)
        .put('/api/keys')
        .send({ name: 'openAI', value: 'sk-test-key-123', expiresAt: '2026-12-31' });

      expect(response.status).toBe(201);
      expect(updateUserKey).toHaveBeenCalledWith({
        userId: 'test-user-123',
        name: 'openAI',
        value: 'sk-test-key-123',
        expiresAt: '2026-12-31',
      });
      expect(updateUserKey).toHaveBeenCalledTimes(1);
    });

    it('should not allow userId override via request body (IDOR prevention)', async () => {
      updateUserKey.mockResolvedValue({});

      const response = await request(app).put('/api/keys').send({
        userId: 'attacker-injected-id',
        name: 'openAI',
        value: 'sk-attacker-key',
      });

      expect(response.status).toBe(201);
      expect(updateUserKey).toHaveBeenCalledWith({
        userId: 'test-user-123',
        name: 'openAI',
        value: 'sk-attacker-key',
        expiresAt: undefined,
      });
    });

    it('should ignore extraneous fields from request body', async () => {
      updateUserKey.mockResolvedValue({});

      const response = await request(app).put('/api/keys').send({
        name: 'openAI',
        value: 'sk-test-key',
        expiresAt: '2026-12-31',
        _id: 'injected-mongo-id',
        __v: 99,
        extra: 'should-be-ignored',
      });

      expect(response.status).toBe(201);
      expect(updateUserKey).toHaveBeenCalledWith({
        userId: 'test-user-123',
        name: 'openAI',
        value: 'sk-test-key',
        expiresAt: '2026-12-31',
      });
    });

    it('should handle missing optional fields', async () => {
      updateUserKey.mockResolvedValue({});

      const response = await request(app)
        .put('/api/keys')
        .send({ name: 'anthropic', value: 'sk-ant-key' });

      expect(response.status).toBe(201);
      expect(updateUserKey).toHaveBeenCalledWith({
        userId: 'test-user-123',
        name: 'anthropic',
        value: 'sk-ant-key',
        expiresAt: undefined,
      });
    });

    it('should return 400 when request body is null', async () => {
      const response = await request(app)
        .put('/api/keys')
        .set('Content-Type', 'application/json')
        .send('null');

      expect(response.status).toBe(400);
      expect(updateUserKey).not.toHaveBeenCalled();
    });
  });

  describe('DELETE /:name', () => {
    it('should delete a user key by name', async () => {
      deleteUserKey.mockResolvedValue({});

      const response = await request(app).delete('/api/keys/openAI');

      expect(response.status).toBe(204);
      expect(deleteUserKey).toHaveBeenCalledWith({
        userId: 'test-user-123',
        name: 'openAI',
      });
      expect(deleteUserKey).toHaveBeenCalledTimes(1);
    });
  });

  describe('DELETE /', () => {
    it('should delete all keys when all=true', async () => {
      deleteUserKey.mockResolvedValue({});

      const response = await request(app).delete('/api/keys?all=true');

      expect(response.status).toBe(204);
      expect(deleteUserKey).toHaveBeenCalledWith({
        userId: 'test-user-123',
        all: true,
      });
    });

    it('should return 400 when all query param is not true', async () => {
      const response = await request(app).delete('/api/keys');

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Specify either all=true to delete.' });
      expect(deleteUserKey).not.toHaveBeenCalled();
    });
  });

  describe('GET /', () => {
    it('should return key expiry for a given key name', async () => {
      const mockExpiry = { expiresAt: '2026-12-31' };
      getUserKeyExpiry.mockResolvedValue(mockExpiry);

      const response = await request(app).get('/api/keys?name=openAI');

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockExpiry);
      expect(getUserKeyExpiry).toHaveBeenCalledWith({
        userId: 'test-user-123',
        name: 'openAI',
      });
    });
  });
});
