// src/routes/admin.monitoring.routes.test.ts
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
import supertest from 'supertest';
import { Request, Response, NextFunction } from 'express';
import adminRouter from './admin.routes';
import {
  createMockUserProfile,
  createMockActivityLogItem,
} from '../tests/utils/mockFactories';
import { UserProfile } from '../types';
import { createTestApp } from '../tests/utils/createTestApp';

const { mockLogger } = vi.hoisted(() => ({
  mockLogger: {
    info: vi.fn(),
    debug: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    child: vi.fn().mockReturnThis(),
  },
}));
vi.mock('../lib/queue', () => ({
  serverAdapter: {
    getRouter: () => (req: Request, res: Response, next: NextFunction) => next(), // Return a dummy express handler
  },
  // Mock other exports if needed
  emailQueue: {},
  cleanupQueue: {},
}));

vi.mock('../services/db/index.db', () => ({
  adminRepo: {
    getActivityLog: vi.fn(),
  },
  flyerRepo: {},
  recipeRepo: {},
  userRepo: {},
  personalizationRepo: {},
  notificationRepo: {},
}));

// Mock the queue service to control worker statuses
vi.mock('../services/queueService.server', () => ({
  flyerWorker: { name: 'flyer-processing', isRunning: vi.fn() },
  emailWorker: { name: 'email-sending', isRunning: vi.fn() },
  analyticsWorker: { name: 'analytics-reporting', isRunning: vi.fn() },
  cleanupWorker: { name: 'file-cleanup', isRunning: vi.fn() },
  weeklyAnalyticsWorker: { name: 'weekly-analytics-reporting', isRunning: vi.fn() },
  flyerQueue: { name: 'flyer-processing', getJobCounts: vi.fn() },
  emailQueue: { name: 'email-sending', getJobCounts: vi.fn() },
  analyticsQueue: { name: 'analytics-reporting', getJobCounts: vi.fn() },
  cleanupQueue: { name: 'file-cleanup', getJobCounts: vi.fn() },
  // FIX: Add the missing weeklyAnalyticsQueue to prevent import errors in admin.routes.ts
  weeklyAnalyticsQueue: { name: 'weekly-analytics-reporting', getJobCounts: vi.fn() },
}));

// Mock other dependencies that are part of the adminRouter setup but not directly tested here
vi.mock('../services/db/flyer.db');
vi.mock('../services/db/recipe.db');
vi.mock('../services/db/user.db');
vi.mock('node:fs/promises');
vi.mock('../services/backgroundJobService');
vi.mock('../services/geocodingService.server');
vi.mock('@bull-board/api');
vi.mock('@bull-board/api/bullMQAdapter');

// Fix: Mock ExpressAdapter as a class to allow `new ExpressAdapter()` to work.
vi.mock('@bull-board/express', () => ({
  ExpressAdapter: class {
    setBasePath = vi.fn();
    getRouter = vi.fn().mockReturnValue((req: Request, res: Response, next: NextFunction) => next());
  },
}));

// Import the mocked modules to control them
import * as queueService from '../services/queueService.server';
import { adminRepo } from '../services/db/index.db';
const mockedQueueService = queueService as Mocked<typeof queueService>;

// Mock the logger
vi.mock('../services/logger.server', () => ({
  logger: mockLogger,
}));

// Mock the passport middleware
vi.mock('./passport.routes', () => ({
  default: {
    authenticate: vi.fn(() => (req: Request, res: Response, next: NextFunction) => {
      if (!req.user) return res.status(401).json({ message: 'Unauthorized' });
      next();
    }),
  },
  isAdmin: (req: Request, res: Response, next: NextFunction) => {
    const user = req.user as UserProfile | undefined;
    if (user && user.role === 'admin') next();
    else res.status(403).json({ message: 'Forbidden: Administrator access required.' });
  },
}));

describe('Admin Monitoring Routes (/api/admin)', () => {
  const adminUser = createMockUserProfile({ role: 'admin' });
  // Create a single app instance with an admin user for all tests in this suite.
  const app = createTestApp({ router: adminRouter, basePath: '/api/admin', authenticatedUser: adminUser });

  // Add a basic error handler to capture errors passed to next(err) and return JSON.
  // This prevents unhandled error crashes in tests and ensures we get the 500 response we expect.
  app.use((err: any, req: any, res: any, next: any) => {
    res.status(err.status || 500).json({ message: err.message, errors: err.errors });
  });

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

  describe('GET /activity-log', () => {
    it('should return a list of activity logs with default pagination', async () => {
      const mockLogs = [createMockActivityLogItem({ action: 'flyer_processed' })];
      vi.mocked(adminRepo.getActivityLog).mockResolvedValue(mockLogs);

      const response = await supertest(app).get('/api/admin/activity-log');

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockLogs);
      expect(adminRepo.getActivityLog).toHaveBeenCalledWith(50, 0, expect.anything());
    });

    it('should use limit and offset query parameters when provided', async () => {
      vi.mocked(adminRepo.getActivityLog).mockResolvedValue([]);

      await supertest(app).get('/api/admin/activity-log?limit=10&offset=20');

      expect(adminRepo.getActivityLog).toHaveBeenCalledWith(10, 20, expect.anything());
    });

    it('should return 400 for invalid limit and offset query parameters', async () => {
      const response = await supertest(app).get('/api/admin/activity-log?limit=abc&offset=-1');
      expect(response.status).toBe(400);
      expect(response.body.errors).toBeDefined();
      expect(response.body.errors.length).toBe(2); // Both limit and offset are invalid
    });
  });

  describe('GET /workers/status', () => {
    it('should return the status of all registered workers', async () => {
      // Arrange: Set the mock status for each worker
      vi.mocked(mockedQueueService.flyerWorker.isRunning).mockReturnValue(true);
      vi.mocked(mockedQueueService.emailWorker.isRunning).mockReturnValue(true);
      vi.mocked(mockedQueueService.analyticsWorker.isRunning).mockReturnValue(false); // Simulate one worker being stopped
      vi.mocked(mockedQueueService.cleanupWorker.isRunning).mockReturnValue(true);
      vi.mocked(mockedQueueService.weeklyAnalyticsWorker.isRunning).mockReturnValue(true);

      // Act
      const response = await supertest(app).get('/api/admin/workers/status');

      // Assert
      expect(response.status).toBe(200);
      expect(response.body).toEqual([
        { name: 'flyer-processing', isRunning: true },
        { name: 'email-sending', isRunning: true },
        { name: 'analytics-reporting', isRunning: false },
        { name: 'file-cleanup', isRunning: true },
        { name: 'weekly-analytics-reporting', isRunning: true },
      ]);
    });
  });

  describe('GET /queues/status', () => {
    it('should return job counts for all registered queues', async () => {
      // Arrange: Set the mock job counts for each queue
      vi.mocked(mockedQueueService.flyerQueue.getJobCounts).mockResolvedValue({ waiting: 5, active: 1, completed: 100, failed: 2, delayed: 0, paused: 0 });
      vi.mocked(mockedQueueService.emailQueue.getJobCounts).mockResolvedValue({ waiting: 0, active: 0, completed: 50, failed: 0, delayed: 0, paused: 0 });
      vi.mocked(mockedQueueService.analyticsQueue.getJobCounts).mockResolvedValue({ waiting: 0, active: 1, completed: 10, failed: 1, delayed: 0, paused: 0 });
      vi.mocked(mockedQueueService.cleanupQueue.getJobCounts).mockResolvedValue({ waiting: 2, active: 0, completed: 25, failed: 0, delayed: 0, paused: 0 });
      vi.mocked(mockedQueueService.weeklyAnalyticsQueue.getJobCounts).mockResolvedValue({ waiting: 1, active: 0, completed: 5, failed: 0, delayed: 0, paused: 0 });

      // Act
      const response = await supertest(app).get('/api/admin/queues/status');

      // Assert
      expect(response.status).toBe(200);
      expect(response.body).toEqual([
        {
          name: 'flyer-processing',
          counts: { waiting: 5, active: 1, completed: 100, failed: 2, delayed: 0, paused: 0 },
        },
        {
          name: 'email-sending',
          counts: { waiting: 0, active: 0, completed: 50, failed: 0, delayed: 0, paused: 0 },
        },
        {
          name: 'analytics-reporting',
          counts: { waiting: 0, active: 1, completed: 10, failed: 1, delayed: 0, paused: 0 },
        },
        { name: 'file-cleanup', counts: { waiting: 2, active: 0, completed: 25, failed: 0, delayed: 0, paused: 0 } },
        {
          name: 'weekly-analytics-reporting',
          counts: { waiting: 1, active: 0, completed: 5, failed: 0, delayed: 0, paused: 0 },
        },
      ]);
    });

    it('should return 500 if fetching queue counts fails', async () => {
      vi.mocked(mockedQueueService.flyerQueue.getJobCounts).mockRejectedValue(new Error('Redis is down'));

      const response = await supertest(app).get('/api/admin/queues/status');
      expect(response.status).toBe(500);
      expect(response.body.message).toBe('Redis is down');
    });
  });
});