// src/services/flyerPersistenceService.server.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { FlyerPersistenceService } from './flyerPersistenceService.server';
import { withTransaction } from './db/connection.db';
import { createFlyerAndItems } from './db/flyer.db';
import { AdminRepository } from './db/admin.db';
import { GamificationRepository } from './db/gamification.db';
import type { FlyerInsert, FlyerItemInsert, Flyer } from '../types';
import type { Logger } from 'pino';
import type { PoolClient } from 'pg';

// Mock dependencies
vi.mock('./db/connection.db', () => ({
  withTransaction: vi.fn(),
}));

vi.mock('./db/flyer.db', () => ({
  createFlyerAndItems: vi.fn(),
}));

vi.mock('./db/admin.db', () => ({
  AdminRepository: vi.fn(),
}));

vi.mock('./db/gamification.db', () => ({
  GamificationRepository: vi.fn(),
}));

describe('FlyerPersistenceService', () => {
  let service: FlyerPersistenceService;
  let mockLogger: Logger;
  let mockClient: PoolClient;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new FlyerPersistenceService();

    mockLogger = {
      info: vi.fn(),
      error: vi.fn(),
      warn: vi.fn(),
      debug: vi.fn(),
      child: vi.fn().mockReturnThis(),
    } as unknown as Logger;

    mockClient = { query: vi.fn() } as unknown as PoolClient;

    // Mock withTransaction to execute the callback immediately with a mock client
    vi.mocked(withTransaction).mockImplementation(async (callback) => {
      return callback(mockClient);
    });
  });

  describe('saveFlyer', () => {
    const mockFlyerData = {
      file_name: 'test.jpg',
      store_name: 'Test Store',
      image_url: 'http://example.com/image.jpg',
      icon_url: 'http://example.com/icon.jpg',
      checksum: 'abc',
      status: 'processed',
      item_count: 0,
      valid_from: '2024-01-01',
      valid_to: '2024-01-07',
      store_address: '123 Test St',
    } as FlyerInsert;

    const mockItemsForDb: FlyerItemInsert[] = [];

    const mockCreatedFlyer = {
      flyer_id: 1,
      file_name: 'test.jpg',
      store_id: 10,
      // ... other fields
    } as Flyer;

    const mockCreatedItems: any[] = [];

    it('should save flyer and items, and log activity if userId is provided', async () => {
      const userId = 'user-123';

      vi.mocked(createFlyerAndItems).mockResolvedValue({
        flyer: mockCreatedFlyer,
        items: mockCreatedItems,
      });

      const mockLogActivity = vi.fn();
      // Mock the AdminRepository constructor to return an object with logActivity
      vi.mocked(AdminRepository).mockImplementation(function () {
        return { logActivity: mockLogActivity } as any;
      });

      const mockAwardAchievement = vi.fn();
      vi.mocked(GamificationRepository).mockImplementation(function () {
        return { awardAchievement: mockAwardAchievement } as any;
      });

      const result = await service.saveFlyer(mockFlyerData, mockItemsForDb, userId, mockLogger);

      expect(withTransaction).toHaveBeenCalled();
      expect(createFlyerAndItems).toHaveBeenCalledWith(
        mockFlyerData,
        mockItemsForDb,
        mockLogger,
        mockClient
      );
      expect(mockLogger.info).toHaveBeenCalledWith(
        expect.stringContaining('Successfully processed flyer')
      );

      // Verify AdminRepository usage
      expect(AdminRepository).toHaveBeenCalledWith(mockClient);
      expect(mockLogActivity).toHaveBeenCalledWith(
        expect.objectContaining({
          userId,
          action: 'flyer_processed',
          displayText: `Processed a new flyer for ${mockFlyerData.store_name}.`,
          details: { flyerId: mockCreatedFlyer.flyer_id, storeName: mockFlyerData.store_name },
        }),
        mockLogger
      );

      // Verify GamificationRepository usage
      expect(GamificationRepository).toHaveBeenCalledWith(mockClient);
      expect(mockAwardAchievement).toHaveBeenCalledWith(userId, 'First-Upload', mockLogger);

      expect(result).toEqual(mockCreatedFlyer);
    });

    it('should save flyer and items, but NOT log activity if userId is undefined', async () => {
      const userId = undefined;

      vi.mocked(createFlyerAndItems).mockResolvedValue({
        flyer: mockCreatedFlyer,
        items: mockCreatedItems,
      });

      const mockLogActivity = vi.fn();
      vi.mocked(AdminRepository).mockImplementation(function () {
        return { logActivity: mockLogActivity } as any;
      });

      const result = await service.saveFlyer(mockFlyerData, mockItemsForDb, userId, mockLogger);

      expect(createFlyerAndItems).toHaveBeenCalled();
      expect(AdminRepository).not.toHaveBeenCalled();
      expect(mockLogActivity).not.toHaveBeenCalled();
      expect(result).toEqual(mockCreatedFlyer);
    });

    it('should propagate errors from createFlyerAndItems', async () => {
      const error = new Error('DB Error');
      vi.mocked(createFlyerAndItems).mockRejectedValue(error);

      await expect(
        service.saveFlyer(mockFlyerData, mockItemsForDb, 'user-1', mockLogger)
      ).rejects.toThrow(error);
    });
  });
});