import { describe, it, expect, vi, beforeEach } from 'vitest';
import supertest from 'supertest';
import { createTestApp } from '../tests/utils/createTestApp';
import { createMockUserProfile } from '../tests/utils/mockFactories';

// Mock dependencies required by admin.routes.ts
vi.mock('../services/db/index.db', () => ({
  adminRepo: {},
  flyerRepo: {},
  recipeRepo: {},
  userRepo: {},
  personalizationRepo: {},
  notificationRepo: {},
}));

vi.mock('../services/backgroundJobService', () => ({
  backgroundJobService: {
    runDailyDealCheck: vi.fn(),
    triggerAnalyticsReport: vi.fn(),
    triggerWeeklyAnalyticsReport: vi.fn(),
  },
}));

vi.mock('../services/queueService.server', () => ({
  flyerQueue: { add: vi.fn(), getJob: vi.fn() },
  emailQueue: { add: vi.fn(), getJob: vi.fn() },
  analyticsQueue: { add: vi.fn(), getJob: vi.fn() },
  cleanupQueue: { add: vi.fn(), getJob: vi.fn() },
  weeklyAnalyticsQueue: { add: vi.fn(), getJob: vi.fn() },
}));

vi.mock('../services/geocodingService.server', () => ({
  geocodingService: { clearGeocodeCache: vi.fn() },
}));

vi.mock('../services/cacheService.server', () => ({
  cacheService: {
    invalidateFlyers: vi.fn(),
    invalidateBrands: vi.fn(),
    invalidateStats: vi.fn(),
  },
}));

vi.mock('../services/logger.server', async () => {
  const { mockLogger, createMockLogger } = await import('../tests/utils/mockLogger');
  return {
    logger: mockLogger,
    createScopedLogger: vi.fn(() => createMockLogger()),
  };
});

vi.mock('@bull-board/api');
vi.mock('@bull-board/api/bullMQAdapter');
vi.mock('@bull-board/express', () => ({
  ExpressAdapter: class {
    setBasePath() {}
    getRouter() {
      return (req: any, res: any, next: any) => next();
    }
  },
}));

vi.mock('node:fs/promises');
vi.mock('../services/queues.server');
vi.mock('../services/workers.server');
vi.mock('../services/monitoringService.server');
vi.mock('../services/userService');
vi.mock('../services/brandService');
vi.mock('../services/receiptService.server');
vi.mock('../services/aiService.server');
vi.mock('../config/env', () => ({
  config: {
    database: { host: 'localhost', port: 5432, user: 'test', password: 'test', name: 'test' },
    redis: { url: 'redis://localhost:6379' },
    auth: { jwtSecret: 'test-secret' },
    server: { port: 3000, host: 'localhost' },
  },
  isAiConfigured: vi.fn().mockReturnValue(false),
  parseConfig: vi.fn(),
}));

// Mock Passport to allow admin access
// Note: admin.routes.ts imports from '../config/passport', so we mock that path
vi.mock('../config/passport', () => ({
  default: {
    authenticate: vi.fn(() => (req: any, res: any, next: any) => {
      req.user = createMockUserProfile({ role: 'admin' });
      next();
    }),
  },
  isAdmin: (req: any, res: any, next: any) => next(),
}));

import adminRouter from './admin.routes';
import { cacheService } from '../services/cacheService.server';
import { mockLogger } from '../tests/utils/mockLogger';

describe('Admin Routes Rate Limiting', () => {
  const app = createTestApp({ router: adminRouter, basePath: '/api/admin' });

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

  describe('Trigger Rate Limiting', () => {
    it('should block requests to /trigger/daily-deal-check after exceeding limit', async () => {
      const limit = 30; // Matches adminTriggerLimiter config

      // Make requests up to the limit
      for (let i = 0; i < limit; i++) {
        await supertest(app)
          .post('/api/admin/trigger/daily-deal-check')
          .set('X-Test-Rate-Limit-Enable', 'true');
      }

      // The next request should be blocked
      const response = await supertest(app)
        .post('/api/admin/trigger/daily-deal-check')
        .set('X-Test-Rate-Limit-Enable', 'true');

      expect(response.status).toBe(429);
      expect(response.text).toContain('Too many administrative triggers');
    });
  });

  describe('Upload Rate Limiting', () => {
    it('should block requests to /brands/:id/logo after exceeding limit', async () => {
      const limit = 20; // Matches adminUploadLimiter config
      const brandId = 1;

      // Make requests up to the limit
      // Note: We don't need to attach a file to test the rate limiter, as it runs before multer
      for (let i = 0; i < limit; i++) {
        await supertest(app)
          .post(`/api/admin/brands/${brandId}/logo`)
          .set('X-Test-Rate-Limit-Enable', 'true');
      }

      const response = await supertest(app)
        .post(`/api/admin/brands/${brandId}/logo`)
        .set('X-Test-Rate-Limit-Enable', 'true');

      expect(response.status).toBe(429);
      expect(response.text).toContain('Too many file uploads');
    });
  });

  describe('POST /system/clear-cache', () => {
    it('should return 200 and clear the cache successfully', async () => {
      vi.mocked(cacheService.invalidateFlyers).mockResolvedValue(5);
      vi.mocked(cacheService.invalidateBrands).mockResolvedValue(3);
      vi.mocked(cacheService.invalidateStats).mockResolvedValue(2);

      const response = await supertest(app).post('/api/admin/system/clear-cache');

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.message).toContain('Successfully cleared the application cache');
      expect(response.body.data.message).toContain('10 keys were removed');
      expect(response.body.data.details).toEqual({
        flyers: 5,
        brands: 3,
        stats: 2,
      });
    });

    it('should return 500 if cache clear fails', async () => {
      const cacheError = new Error('Redis connection failed');
      vi.mocked(cacheService.invalidateFlyers).mockRejectedValue(cacheError);

      const response = await supertest(app).post('/api/admin/system/clear-cache');

      expect(response.status).toBe(500);
      expect(mockLogger.error).toHaveBeenCalledWith(
        { error: cacheError },
        '[Admin] Failed to clear application cache.',
      );
    });
  });
});
