import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { AggregatedMetric, MetricType, TimeGranularity } from '../entities/aggregated-metric.entity';
import { TrendsService } from './trends.service';
import { createMockRepository, MockRepository } from '@test/mocks';
import { createAggregatedMetricSeries, createAggregatedMetric } from '@test/fixtures';

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

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

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

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

  describe('getTrends', () => {
    it('returns trend data points with summary', async () => {
      const metricSeries = createAggregatedMetricSeries(7, {
        metricType: MetricType.PAGE_VIEWS,
        granularity: TimeGranularity.DAY,
      });

      mockRepository.find.mockResolvedValue(metricSeries as AggregatedMetric[]);

      const result = await service.getTrends({
        metric: 'page_views',
        startDate: '2026-01-15',
        endDate: '2026-01-21',
        granularity: 'day',
      });

      expect(result.metric).toBe('page_views');
      expect(result.granularity).toBe('day');
      expect(result.data).toHaveLength(7);
      expect(result.data[0]).toEqual({
        timestamp: expect.any(Date),
        value: 100,
        count: 100,
      });

      expect(mockRepository.find).toHaveBeenCalledWith({
        where: {
          metricType: 'page_views',
          granularity: 'day',
          timestamp: expect.any(Object),
          dimension: undefined,
        },
        order: { timestamp: 'ASC' },
      });
    });

    it('calculates summary correctly (total, average, min, max)', async () => {
      const metricSeries = [
        createAggregatedMetric({ value: 100, count: 100 }),
        createAggregatedMetric({ value: 200, count: 200 }),
        createAggregatedMetric({ value: 150, count: 150 }),
        createAggregatedMetric({ value: 300, count: 300 }),
        createAggregatedMetric({ value: 250, count: 250 }),
      ];

      mockRepository.find.mockResolvedValue(metricSeries as AggregatedMetric[]);

      const result = await service.getTrends({
        metric: 'sessions',
        startDate: '2026-01-15',
        endDate: '2026-01-19',
        granularity: 'day',
      });

      expect(result.summary.total).toBe(1000);
      expect(result.summary.average).toBe(200);
      expect(result.summary.min).toBe(100);
      expect(result.summary.max).toBe(300);
    });

    it('handles empty data (returns zeros in summary)', async () => {
      mockRepository.find.mockResolvedValue([]);

      const result = await service.getTrends({
        metric: 'page_views',
        startDate: '2026-01-15',
        endDate: '2026-01-21',
        granularity: 'day',
      });

      expect(result.data).toHaveLength(0);
      expect(result.summary.total).toBe(0);
      expect(result.summary.average).toBe(0);
      expect(result.summary.min).toBe(0);
      expect(result.summary.max).toBe(0);
    });

    it('handles single data point correctly', async () => {
      const singleMetric = [createAggregatedMetric({ value: 500, count: 500 })];

      mockRepository.find.mockResolvedValue(singleMetric as AggregatedMetric[]);

      const result = await service.getTrends({
        metric: 'unique_visitors',
        startDate: '2026-01-15',
        endDate: '2026-01-15',
        granularity: 'day',
      });

      expect(result.data).toHaveLength(1);
      expect(result.summary.total).toBe(500);
      expect(result.summary.average).toBe(500);
      expect(result.summary.min).toBe(500);
      expect(result.summary.max).toBe(500);
    });
  });

  describe('compareTrends', () => {
    it('returns current and previous trends with percentage change', async () => {
      const currentMetrics = createAggregatedMetricSeries(7, {
        metricType: MetricType.PAGE_VIEWS,
        granularity: TimeGranularity.DAY,
        value: 200,
        count: 200,
      });

      const previousMetrics = createAggregatedMetricSeries(7, {
        metricType: MetricType.PAGE_VIEWS,
        granularity: TimeGranularity.DAY,
        value: 150,
        count: 150,
      });

      mockRepository.find
        .mockResolvedValueOnce(currentMetrics as AggregatedMetric[])
        .mockResolvedValueOnce(previousMetrics as AggregatedMetric[]);

      const result = await service.compareTrends(
        {
          metric: 'page_views',
          startDate: '2026-01-15',
          endDate: '2026-01-21',
          granularity: 'day',
        },
        '2026-01-08',
        '2026-01-14',
      );

      expect(result.current.data).toHaveLength(7);
      expect(result.previous.data).toHaveLength(7);
      expect(result.current.summary.total).toBe(1400);
      expect(result.previous.summary.total).toBe(1050);

      const expectedChange = ((1400 - 1050) / 1050) * 100;
      expect(result.change).toBeCloseTo(expectedChange, 2);
    });

    it('handles zero previous total (change = 0)', async () => {
      const currentMetrics = createAggregatedMetricSeries(7, {
        metricType: MetricType.SESSIONS,
        granularity: TimeGranularity.DAY,
        value: 100,
        count: 100,
      });

      mockRepository.find
        .mockResolvedValueOnce(currentMetrics as AggregatedMetric[])
        .mockResolvedValueOnce([]);

      const result = await service.compareTrends(
        {
          metric: 'sessions',
          startDate: '2026-01-15',
          endDate: '2026-01-21',
          granularity: 'day',
        },
        '2026-01-08',
        '2026-01-14',
      );

      expect(result.current.summary.total).toBe(700);
      expect(result.previous.summary.total).toBe(0);
      expect(result.change).toBe(0);
    });

    it('calculates negative change correctly', async () => {
      const currentMetrics = createAggregatedMetricSeries(5, {
        metricType: MetricType.REVENUE,
        granularity: TimeGranularity.DAY,
        value: 80,
        count: 80,
      });

      const previousMetrics = createAggregatedMetricSeries(5, {
        metricType: MetricType.REVENUE,
        granularity: TimeGranularity.DAY,
        value: 100,
        count: 100,
      });

      mockRepository.find
        .mockResolvedValueOnce(currentMetrics as AggregatedMetric[])
        .mockResolvedValueOnce(previousMetrics as AggregatedMetric[]);

      const result = await service.compareTrends(
        {
          metric: 'revenue',
          startDate: '2026-01-20',
          endDate: '2026-01-24',
          granularity: 'day',
        },
        '2026-01-13',
        '2026-01-17',
      );

      expect(result.current.summary.total).toBe(400);
      expect(result.previous.summary.total).toBe(500);

      const expectedChange = ((400 - 500) / 500) * 100;
      expect(result.change).toBeCloseTo(expectedChange, 2);
      expect(result.change).toBeLessThan(0);
    });

    it('handles equal current and previous totals (change = 0)', async () => {
      const metrics = createAggregatedMetricSeries(3, {
        metricType: MetricType.UNIQUE_VISITORS,
        granularity: TimeGranularity.DAY,
        value: 150,
        count: 150,
      });

      mockRepository.find
        .mockResolvedValueOnce(metrics as AggregatedMetric[])
        .mockResolvedValueOnce(metrics as AggregatedMetric[]);

      const result = await service.compareTrends(
        {
          metric: 'unique_visitors',
          startDate: '2026-01-15',
          endDate: '2026-01-17',
          granularity: 'day',
        },
        '2026-01-12',
        '2026-01-14',
      );

      expect(result.current.summary.total).toBe(450);
      expect(result.previous.summary.total).toBe(450);
      expect(result.change).toBe(0);
    });
  });
});
