import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { DataSource } from '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 },
        { provide: DataSource, useValue: { query: () => Promise.resolve([]) } },
      ],
    }).compile();

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

  describe('getTrends', () => {
    it('returns trend data points with summary', async () => {
      const rows = Array.from({ length: 7 }, (_, i) => {
        const d = new Date('2026-01-15T00:00:00Z');
        d.setDate(d.getDate() + i);
        return { timestamp: d, value: String(100 + i * 10) };
      });

      mockRepository.query.mockResolvedValue(rows);

      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.query).toHaveBeenCalled();
    });

    it('calculates summary correctly (total, average, min, max)', async () => {
      const rows = [100, 200, 150, 300, 250].map((v, i) => ({
        timestamp: new Date(),
        value: String(v),
      }));

      mockRepository.query.mockResolvedValue(rows);

      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.query.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 () => {
      mockRepository.query.mockResolvedValue([
        { timestamp: new Date('2026-01-15T00:00:00Z'), value: '500' },
      ]);

      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 buildRows = (n: number, value: number) =>
        Array.from({ length: n }, (_, i) => {
          const d = new Date('2026-01-15T00:00:00Z');
          d.setDate(d.getDate() + i);
          return { timestamp: d, value: String(value) };
        });

      mockRepository.query
        .mockResolvedValueOnce(buildRows(7, 200))
        .mockResolvedValueOnce(buildRows(7, 150));

      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 buildRows = (n: number, value: number) =>
        Array.from({ length: n }, (_, i) => {
          const d = new Date('2026-01-15T00:00:00Z');
          d.setDate(d.getDate() + i);
          return { timestamp: d, value: String(value) };
        });

      mockRepository.query
        .mockResolvedValueOnce(buildRows(7, 100))
        .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 buildRows = (n: number, value: number) =>
        Array.from({ length: n }, (_, i) => {
          const d = new Date('2026-01-15T00:00:00Z');
          d.setDate(d.getDate() + i);
          return { timestamp: d, value: String(value) };
        });
      const rows = buildRows(3, 150);

      mockRepository.query
        .mockResolvedValueOnce(rows)
        .mockResolvedValueOnce(rows);

      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);
    });
  });
});
