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

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

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

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

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

  describe('getRetentionCohorts', () => {
    it('returns cohort structure with correct period labels for day granularity', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-15',
        '2026-01-17',
        'day',
      );

      expect(result.periods).toHaveLength(7);
      expect(result.periods).toEqual([
        'Day 0',
        'Day 1',
        'Day 2',
        'Day 3',
        'Day 4',
        'Day 5',
        'Day 6',
      ]);

      expect(result.cohorts.length).toBeGreaterThan(0);
      expect(result.averageRetention).toHaveLength(7);
    });

    it('returns cohort structure with correct period labels for week granularity', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-01',
        '2026-01-31',
        'week',
      );

      expect(result.periods).toHaveLength(12);
      expect(result.periods).toEqual([
        'Week 0',
        'Week 1',
        'Week 2',
        'Week 3',
        'Week 4',
        'Week 5',
        'Week 6',
        'Week 7',
        'Week 8',
        'Week 9',
        'Week 10',
        'Week 11',
      ]);

      expect(result.averageRetention).toHaveLength(12);
    });

    it('returns cohort structure with correct period labels for month granularity', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-01',
        '2026-06-30',
        'month',
      );

      expect(result.periods).toHaveLength(6);
      expect(result.periods).toEqual([
        'Month 0',
        'Month 1',
        'Month 2',
        'Month 3',
        'Month 4',
        'Month 5',
      ]);

      expect(result.averageRetention).toHaveLength(6);
    });

    it('returns correct number of periods per granularity (day=7, week=12, month=6)', async () => {
      const dayResult = await service.getRetentionCohorts(
        '2026-01-15',
        '2026-01-20',
        'day',
      );
      expect(dayResult.periods).toHaveLength(7);

      const weekResult = await service.getRetentionCohorts(
        '2026-01-01',
        '2026-03-31',
        'week',
      );
      expect(weekResult.periods).toHaveLength(12);

      const monthResult = await service.getRetentionCohorts(
        '2026-01-01',
        '2026-12-31',
        'month',
      );
      expect(monthResult.periods).toHaveLength(6);
    });

    it('cohorts have dates between start and end', async () => {
      const startDate = '2026-01-15';
      const endDate = '2026-01-17';

      const result = await service.getRetentionCohorts(startDate, endDate, 'day');

      expect(result.cohorts.length).toBeGreaterThan(0);

      const start = new Date(startDate);
      const end = new Date(endDate);

      for (const cohort of result.cohorts) {
        const cohortDate = new Date(cohort.cohortDate);
        expect(cohortDate.getTime()).toBeGreaterThanOrEqual(start.getTime());
        expect(cohortDate.getTime()).toBeLessThanOrEqual(end.getTime());
      }
    });

    it('each cohort has correct structure', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-15',
        '2026-01-16',
        'day',
      );

      expect(result.cohorts.length).toBeGreaterThan(0);

      for (const cohort of result.cohorts) {
        expect(cohort).toHaveProperty('cohortDate');
        expect(cohort).toHaveProperty('cohortSize');
        expect(cohort).toHaveProperty('retentionByPeriod');
        expect(typeof cohort.cohortDate).toBe('string');
        expect(typeof cohort.cohortSize).toBe('number');
        expect(Array.isArray(cohort.retentionByPeriod)).toBe(true);
        expect(cohort.retentionByPeriod).toHaveLength(7);
      }
    });

    it('generates cohorts for day granularity incrementing by 1 day', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-15',
        '2026-01-17',
        'day',
      );

      expect(result.cohorts.length).toBe(3);
      expect(result.cohorts[0]?.cohortDate).toBe('2026-01-15');
      expect(result.cohorts[1]?.cohortDate).toBe('2026-01-16');
      expect(result.cohorts[2]?.cohortDate).toBe('2026-01-17');
    });

    it('generates cohorts for week granularity incrementing by 7 days', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-01',
        '2026-01-21',
        'week',
      );

      expect(result.cohorts.length).toBeGreaterThan(0);

      const dates = result.cohorts.map((c) => new Date(c.cohortDate));

      for (let i = 1; i < dates.length; i++) {
        const previous = dates[i - 1];
        const current = dates[i];
        if (previous && current) {
          const diff = (current.getTime() - previous.getTime()) / (1000 * 60 * 60 * 24);
          expect(diff).toBe(7);
        }
      }
    });

    it('initializes retention arrays with zeros', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-15',
        '2026-01-16',
        'day',
      );

      for (const cohort of result.cohorts) {
        expect(cohort.retentionByPeriod).toEqual([0, 0, 0, 0, 0, 0, 0]);
      }
    });

    it('initializes average retention with zeros', async () => {
      const result = await service.getRetentionCohorts(
        '2026-01-15',
        '2026-01-16',
        'day',
      );

      expect(result.averageRetention).toEqual([0, 0, 0, 0, 0, 0, 0]);
    });
  });

  describe('getBehavioralCohorts', () => {
    it('returns behavioral cohort structure', async () => {
      const result = await service.getBehavioralCohorts(
        '2026-01-01',
        '2026-01-31',
        'activity_level',
      );

      expect(result).toHaveProperty('segments');
      expect(result).toHaveProperty('startDate');
      expect(result).toHaveProperty('endDate');
      expect(result).toHaveProperty('segmentBy');
      expect(Array.isArray(result.segments)).toBe(true);
    });

    it('passes through startDate, endDate, segmentBy', async () => {
      const startDate = '2026-01-15';
      const endDate = '2026-02-15';
      const segmentBy = 'feature_usage';

      const result = await service.getBehavioralCohorts(startDate, endDate, segmentBy);

      expect(result.startDate).toBe(startDate);
      expect(result.endDate).toBe(endDate);
      expect(result.segmentBy).toBe(segmentBy);
    });

    it('returns empty segments array', async () => {
      const result = await service.getBehavioralCohorts(
        '2026-01-01',
        '2026-01-31',
        'engagement_score',
      );

      expect(result.segments).toEqual([]);
    });

    it('handles different segmentBy values', async () => {
      const segmentByValues = [
        'activity_level',
        'feature_usage',
        'engagement_score',
        'purchase_frequency',
      ];

      for (const segmentBy of segmentByValues) {
        const result = await service.getBehavioralCohorts(
          '2026-01-01',
          '2026-01-31',
          segmentBy,
        );

        expect(result.segmentBy).toBe(segmentBy);
        expect(result.segments).toEqual([]);
      }
    });
  });
});
