// src/routes/deals.routes.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import supertest from 'supertest';
import type { Request, Response, NextFunction } from 'express';
import { createMockUserProfile, createMockWatchedItemDeal } from '../tests/utils/mockFactories';
import type { WatchedItemDeal } from '../types';
import { createTestApp } from '../tests/utils/createTestApp';

// 1. Mock the Service Layer directly.
vi.mock('../services/db/deals.db', () => ({
  dealsRepo: {
    findBestPricesForWatchedItems: vi.fn(),
  },
}));

// Import the router and mocked repo AFTER all mocks are defined.
import dealsRouter from './deals.routes';
import { dealsRepo } from '../services/db/deals.db';
import { mockLogger } from '../tests/utils/mockLogger';

// Mock the logger to keep test output clean
vi.mock('../services/logger.server', async () => ({
  // Use async import to avoid hoisting issues with mockLogger
  logger: (await import('../tests/utils/mockLogger')).mockLogger,
}));

// Mock the passport middleware
vi.mock('../config/passport', () => ({
  default: {
    authenticate: vi.fn(() => (req: Request, res: Response, next: NextFunction) => {
      // If req.user is not set by the test setup, simulate unauthenticated access.
      if (!req.user) {
        return res.status(401).json({ message: 'Unauthorized' });
      }
      // If req.user is set, proceed as an authenticated user.
      next();
    }),
  },
}));

// Define a reusable matcher for the logger object.
const expectLogger = expect.objectContaining({
  info: expect.any(Function),
  error: expect.any(Function),
});

describe('Deals Routes (/api/users/deals)', () => {
  const mockUser = createMockUserProfile({ user: { user_id: 'user-123', email: 'test@test.com' } });
  const basePath = '/api/users/deals';
  const authenticatedApp = createTestApp({
    router: dealsRouter,
    basePath,
    authenticatedUser: mockUser,
  });
  const unauthenticatedApp = createTestApp({ router: dealsRouter, basePath });

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

  describe('GET /best-watched-prices', () => {
    it('should return 401 Unauthorized if user is not authenticated', async () => {
      const response = await supertest(unauthenticatedApp).get(
        '/api/users/deals/best-watched-prices',
      );
      expect(response.status).toBe(401);
    });

    it('should return a list of deals for an authenticated user', async () => {
      const mockDeals: WatchedItemDeal[] = [createMockWatchedItemDeal({ item_name: 'Apples' })];
      vi.mocked(dealsRepo.findBestPricesForWatchedItems).mockResolvedValue(mockDeals);

      const response = await supertest(authenticatedApp).get(
        '/api/users/deals/best-watched-prices',
      );

      expect(response.status).toBe(200);
      expect(response.body.data).toEqual(mockDeals);
      expect(dealsRepo.findBestPricesForWatchedItems).toHaveBeenCalledWith(
        mockUser.user.user_id,
        expectLogger,
      );
      expect(mockLogger.info).toHaveBeenCalledWith(
        { dealCount: 1 },
        'Successfully fetched best watched item deals.',
      );
    });

    it('should return 500 if the database call fails', async () => {
      const dbError = new Error('DB Error');
      vi.mocked(dealsRepo.findBestPricesForWatchedItems).mockRejectedValue(dbError);

      const response = await supertest(authenticatedApp).get(
        '/api/users/deals/best-watched-prices',
      );
      expect(response.status).toBe(500);
      expect(response.body.error.message).toBe('DB Error');
      expect(mockLogger.error).toHaveBeenCalledWith(
        { error: dbError },
        'Error fetching best watched item deals.',
      );
    });
  });

  describe('Rate Limiting', () => {
    it('should apply userReadLimiter to GET /best-watched-prices', async () => {
      vi.mocked(dealsRepo.findBestPricesForWatchedItems).mockResolvedValue([]);

      const response = await supertest(authenticatedApp)
        .get('/api/users/deals/best-watched-prices')
        .set('X-Test-Rate-Limit-Enable', 'true');

      expect(response.status).toBe(200);
      expect(response.headers).toHaveProperty('ratelimit-limit');
      expect(parseInt(response.headers['ratelimit-limit'])).toBe(100);
    });
  });
});
