import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { AggregatedMetric } from '../entities/aggregated-metric.entity';
import { FunnelsService } from './funnels.service';
import { createMockRepository, MockRepository } from '@test/mocks';

describe('FunnelsService', () => {
  let service: FunnelsService;
  let mockRepository: MockRepository<AggregatedMetric>;

  beforeEach(async () => {
    mockRepository = createMockRepository<AggregatedMetric>();

    const module = await Test.createTestingModule({
      providers: [
        FunnelsService,
        { provide: getRepositoryToken(AggregatedMetric), useValue: mockRepository },
      ],
    }).compile();

    service = module.get<FunnelsService>(FunnelsService);
  });

  describe('analyzeFunnel', () => {
    it('returns funnel steps with conversion rates', async () => {
      mockRepository.queryBuilder.getRawOne
        .mockResolvedValueOnce({ total: '1000' })
        .mockResolvedValueOnce({ total: '600' })
        .mockResolvedValueOnce({ total: '300' })
        .mockResolvedValueOnce({ total: '150' });

      const result = await service.analyzeFunnel({
        steps: [
          { name: 'Landing Page', eventType: 'page_view_landing' },
          { name: 'Signup Started', eventType: 'signup_started' },
          { name: 'Email Verified', eventType: 'email_verified' },
          { name: 'Profile Completed', eventType: 'profile_completed' },
        ],
        startDate: '2026-01-01',
        endDate: '2026-01-31',
      });

      expect(result.steps).toHaveLength(4);

      expect(result.steps[0]).toEqual({
        name: 'Landing Page',
        eventType: 'page_view_landing',
        count: 1000,
        conversionRate: 100,
        dropoffRate: 0,
      });

      expect(result.steps[1]).toEqual({
        name: 'Signup Started',
        eventType: 'signup_started',
        count: 600,
        conversionRate: 60,
        dropoffRate: 40,
      });

      expect(result.steps[2]).toEqual({
        name: 'Email Verified',
        eventType: 'email_verified',
        count: 300,
        conversionRate: 50,
        dropoffRate: 50,
      });

      expect(result.steps[3]).toEqual({
        name: 'Profile Completed',
        eventType: 'profile_completed',
        count: 150,
        conversionRate: 50,
        dropoffRate: 50,
      });

      expect(mockRepository.createQueryBuilder).toHaveBeenCalledTimes(4);
      expect(mockRepository.queryBuilder.select).toHaveBeenCalledWith(
        'COALESCE(SUM(m.count), 0)',
        'total',
      );
    });

    it('first step has 0 dropoff rate', async () => {
      mockRepository.queryBuilder.getRawOne
        .mockResolvedValueOnce({ total: '500' })
        .mockResolvedValueOnce({ total: '250' });

      const result = await service.analyzeFunnel({
        steps: [
          { name: 'Product View', eventType: 'product_view' },
          { name: 'Add to Cart', eventType: 'cart_add' },
        ],
        startDate: '2026-01-01',
        endDate: '2026-01-31',
      });

      expect(result.steps[0]?.dropoffRate).toBe(0);
      expect(result.steps[0]?.conversionRate).toBe(100);
      expect(result.steps[1]?.dropoffRate).toBe(50);
    });

    it('calculates overall conversion rate (totalCompleted / totalStarted * 100)', async () => {
      mockRepository.queryBuilder.getRawOne
        .mockResolvedValueOnce({ total: '2000' })
        .mockResolvedValueOnce({ total: '1200' })
        .mockResolvedValueOnce({ total: '800' })
        .mockResolvedValueOnce({ total: '400' });

      const result = await service.analyzeFunnel({
        steps: [
          { name: 'Step 1', eventType: 'step1' },
          { name: 'Step 2', eventType: 'step2' },
          { name: 'Step 3', eventType: 'step3' },
          { name: 'Step 4', eventType: 'step4' },
        ],
        startDate: '2026-01-01',
        endDate: '2026-01-31',
      });

      expect(result.totalStarted).toBe(2000);
      expect(result.totalCompleted).toBe(400);
      expect(result.overallConversionRate).toBe(20);
    });

    it('handles zero counts (conversion rate = 0)', async () => {
      mockRepository.queryBuilder.getRawOne
        .mockResolvedValueOnce({ total: '0' })
        .mockResolvedValueOnce({ total: '0' })
        .mockResolvedValueOnce({ total: '0' });

      const result = await service.analyzeFunnel({
        steps: [
          { name: 'Step 1', eventType: 'step1' },
          { name: 'Step 2', eventType: 'step2' },
          { name: 'Step 3', eventType: 'step3' },
        ],
        startDate: '2026-01-01',
        endDate: '2026-01-31',
      });

      expect(result.steps[0]?.count).toBe(0);
      expect(result.steps[0]?.conversionRate).toBe(0);
      expect(result.steps[1]?.conversionRate).toBe(0);
      expect(result.totalStarted).toBe(0);
      expect(result.totalCompleted).toBe(0);
      expect(result.overallConversionRate).toBe(0);
    });

    it('handles null query results', async () => {
      mockRepository.queryBuilder.getRawOne
        .mockResolvedValueOnce(null)
        .mockResolvedValueOnce(null);

      const result = await service.analyzeFunnel({
        steps: [
          { name: 'Step 1', eventType: 'step1' },
          { name: 'Step 2', eventType: 'step2' },
        ],
        startDate: '2026-01-01',
        endDate: '2026-01-31',
      });

      expect(result.steps[0]?.count).toBe(0);
      expect(result.steps[1]?.count).toBe(0);
      expect(result.totalStarted).toBe(0);
      expect(result.totalCompleted).toBe(0);
    });

    it('applies correct query parameters', async () => {
      mockRepository.queryBuilder.getRawOne.mockResolvedValue({ total: '100' });

      await service.analyzeFunnel({
        steps: [{ name: 'Test Step', eventType: 'test_event' }],
        startDate: '2026-01-15',
        endDate: '2026-01-20',
      });

      expect(mockRepository.queryBuilder.where).toHaveBeenCalledWith(
        'm.dimension = :dimension',
        { dimension: 'event_type' },
      );
      expect(mockRepository.queryBuilder.andWhere).toHaveBeenCalledWith(
        'm.dimensionValue = :eventType',
        { eventType: 'test_event' },
      );
      expect(mockRepository.queryBuilder.andWhere).toHaveBeenCalledWith(
        'm.timestamp BETWEEN :start AND :end',
        {
          start: new Date('2026-01-15'),
          end: new Date('2026-01-20'),
        },
      );
    });
  });

  describe('getPresets', () => {
    it('returns signup and purchase funnel presets', async () => {
      const presets = await service.getPresets();

      expect(presets).toHaveLength(2);
      expect(presets[0]).toEqual({
        id: 'signup',
        name: 'Signup Funnel',
        steps: [
          { name: 'Landing Page', eventType: 'page_view_landing' },
          { name: 'Signup Started', eventType: 'signup_started' },
          { name: 'Email Verified', eventType: 'email_verified' },
          { name: 'Profile Completed', eventType: 'profile_completed' },
        ],
      });

      expect(presets[1]).toEqual({
        id: 'purchase',
        name: 'Purchase Funnel',
        steps: [
          { name: 'Product View', eventType: 'product_view' },
          { name: 'Add to Cart', eventType: 'cart_add' },
          { name: 'Checkout Started', eventType: 'checkout_started' },
          { name: 'Purchase Complete', eventType: 'purchase' },
        ],
      });
    });

    it('each preset has name, id, and steps array', async () => {
      const presets = await service.getPresets();

      for (const preset of presets) {
        expect(preset).toHaveProperty('id');
        expect(preset).toHaveProperty('name');
        expect(preset).toHaveProperty('steps');
        expect(Array.isArray(preset.steps)).toBe(true);
        expect(preset.steps.length).toBeGreaterThan(0);

        for (const step of preset.steps) {
          expect(step).toHaveProperty('name');
          expect(step).toHaveProperty('eventType');
        }
      }
    });
  });
});
