import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository, Between } from 'typeorm';
import { PredictionService } from './prediction.service';
import { Activity, ActivityType } from '../../database/entities/activity.entity';
import { Child } from '../../database/entities/child.entity';

describe('PredictionService', () => {
  let service: PredictionService;
  let activityRepository: Repository<Activity>;
  let childRepository: Repository<Child>;

  const mockChild = {
    id: 'child_123',
    name: 'Baby Jane',
    birthDate: new Date(Date.now() - 3 * 30 * 24 * 60 * 60 * 1000), // 3 months old
    familyId: 'family_123',
  };

  const mockActivityRepository = {
    find: jest.fn(),
  };

  const mockChildRepository = {
    findOne: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PredictionService,
        {
          provide: getRepositoryToken(Activity),
          useValue: mockActivityRepository,
        },
        {
          provide: getRepositoryToken(Child),
          useValue: mockChildRepository,
        },
      ],
    }).compile();

    service = module.get<PredictionService>(PredictionService);
    activityRepository = module.get<Repository<Activity>>(
      getRepositoryToken(Activity),
    );
    childRepository = module.get<Repository<Child>>(
      getRepositoryToken(Child),
    );

    jest.clearAllMocks();
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('generatePredictions', () => {
    it('should generate predictions for a child', async () => {
      const sleepActivities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(Date.now() - 10 * 60 * 60 * 1000),
          endedAt: new Date(Date.now() - 8 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(Date.now() - 6 * 60 * 60 * 1000),
          endedAt: new Date(Date.now() - 5 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(Date.now() - 3 * 60 * 60 * 1000),
          endedAt: new Date(Date.now() - 2 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(Date.now() - 1 * 60 * 60 * 1000),
          endedAt: new Date(Date.now() - 30 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(Date.now() - 15 * 60 * 1000),
          endedAt: new Date(),
        },
      ];

      const feedingActivities = [
        {
          type: ActivityType.FEEDING,
          startedAt: new Date(Date.now() - 9 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.FEEDING,
          startedAt: new Date(Date.now() - 6 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.FEEDING,
          startedAt: new Date(Date.now() - 3 * 60 * 60 * 1000),
        },
      ];

      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue([
        ...sleepActivities,
        ...feedingActivities,
      ]);

      const result = await service.generatePredictions('child_123');

      expect(result).toHaveProperty('sleep');
      expect(result).toHaveProperty('feeding');
      expect(result).toHaveProperty('generatedAt');
      expect(result.generatedAt).toBeInstanceOf(Date);
    });

    it('should throw error if child not found', async () => {
      mockChildRepository.findOne.mockResolvedValue(null);

      await expect(service.generatePredictions('child_123')).rejects.toThrow(
        'Child not found',
      );
    });
  });

  describe('predictNextSleep', () => {
    it('should return low confidence with insufficient data', async () => {
      const activities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(),
          endedAt: new Date(),
        },
      ];

      const result = await (service as any).predictNextSleep(
        activities,
        mockChild,
      );

      expect(result.nextNapTime).toBeNull();
      expect(result.nextNapConfidence).toBe(0);
      expect(result.nextBedtime).toBeNull();
      expect(result.bedtimeConfidence).toBe(0);
      expect(result.reasoning).toContain('Insufficient data');
      expect(result.optimalWakeWindows).toHaveLength(3);
    });

    it('should predict next nap time based on wake windows', async () => {
      const baseTime = Date.now();
      const sleepActivities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(baseTime - 10 * 60 * 60 * 1000),
          endedAt: new Date(baseTime - 9 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(baseTime - 7.5 * 60 * 60 * 1000),
          endedAt: new Date(baseTime - 6.5 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(baseTime - 5 * 60 * 60 * 1000),
          endedAt: new Date(baseTime - 4 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(baseTime - 2.5 * 60 * 60 * 1000),
          endedAt: new Date(baseTime - 1.5 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(baseTime - 30 * 60 * 1000),
          endedAt: new Date(baseTime - 15 * 60 * 1000),
        },
      ];

      const result = await (service as any).predictNextSleep(
        sleepActivities,
        mockChild,
      );

      expect(result.nextNapTime).toBeInstanceOf(Date);
      expect(result.nextNapConfidence).toBeGreaterThan(0);
      expect(result.nextNapConfidence).toBeLessThanOrEqual(1);
    });

    it('should predict bedtime based on historical patterns', async () => {
      const baseTime = Date.now();
      const today = new Date(baseTime);
      today.setHours(12, 0, 0, 0); // Noon today

      const sleepActivities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(today.getTime() - 3 * 24 * 60 * 60 * 1000),
          endedAt: new Date(
            today.getTime() - 3 * 24 * 60 * 60 * 1000 + 8 * 60 * 60 * 1000,
          ),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(today.getTime() - 3 * 24 * 60 * 60 * 1000),
          endedAt: new Date(
            today.getTime() - 3 * 24 * 60 * 60 * 1000 + 30 * 60 * 1000,
          ),
        },
        // Night sleeps at 8PM
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(
            today.getTime() - 3 * 24 * 60 * 60 * 1000 + 20 * 60 * 60 * 1000,
          ),
          endedAt: new Date(
            today.getTime() - 2 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000,
          ),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(
            today.getTime() - 2 * 24 * 60 * 60 * 1000 + 20 * 60 * 60 * 1000,
          ),
          endedAt: new Date(
            today.getTime() - 1 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000,
          ),
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date(
            today.getTime() - 1 * 24 * 60 * 60 * 1000 + 20 * 60 * 60 * 1000,
          ),
          endedAt: new Date(today.getTime() + 6 * 60 * 60 * 1000),
        },
      ];

      const result = await (service as any).predictNextSleep(
        sleepActivities,
        mockChild,
      );

      expect(result.nextBedtime).toBeInstanceOf(Date);
      expect(result.bedtimeConfidence).toBeGreaterThan(0);
      expect(result.bedtimeConfidence).toBeLessThanOrEqual(1);
    });

    it('should calculate high confidence for consistent patterns', async () => {
      // Very consistent wake windows of exactly 90 minutes
      const baseTime = Date.now();
      const sleepActivities = Array.from({ length: 10 }, (_, i) => ({
        type: ActivityType.SLEEP,
        startedAt: new Date(baseTime - (10 - i) * 2.5 * 60 * 60 * 1000),
        endedAt: new Date(baseTime - (10 - i) * 2.5 * 60 * 60 * 1000 + 60 * 60 * 1000),
      }));

      const result = await (service as any).predictNextSleep(
        sleepActivities,
        mockChild,
      );

      expect(result.nextNapConfidence).toBeGreaterThan(0.8);
      expect(result.reasoning).toContain('High confidence');
    });
  });

  describe('predictNextFeeding', () => {
    it('should return low confidence with insufficient data', async () => {
      const activities = [
        {
          type: ActivityType.FEEDING,
          startedAt: new Date(),
        },
      ];

      const result = await (service as any).predictNextFeeding(
        activities,
        mockChild,
      );

      expect(result.nextFeedingTime).toBeNull();
      expect(result.confidence).toBe(0);
      expect(result.reasoning).toContain('Insufficient data');
      expect(result.expectedInterval).toBeGreaterThan(0);
    });

    it('should predict next feeding time based on intervals', async () => {
      const baseTime = Date.now();
      const feedingActivities = [
        {
          type: ActivityType.FEEDING,
          startedAt: new Date(baseTime - 9 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.FEEDING,
          startedAt: new Date(baseTime - 6 * 60 * 60 * 1000),
        },
        {
          type: ActivityType.FEEDING,
          startedAt: new Date(baseTime - 3 * 60 * 60 * 1000),
        },
      ];

      const result = await (service as any).predictNextFeeding(
        feedingActivities,
        mockChild,
      );

      expect(result.nextFeedingTime).toBeInstanceOf(Date);
      expect(result.confidence).toBeGreaterThan(0);
      expect(result.confidence).toBeLessThanOrEqual(1);
      expect(result.expectedInterval).toBe(3);
    });

    it('should calculate high confidence for consistent feeding patterns', async () => {
      // Very consistent 3-hour intervals
      const baseTime = Date.now();
      const feedingActivities = Array.from({ length: 10 }, (_, i) => ({
        type: ActivityType.FEEDING,
        startedAt: new Date(baseTime - (10 - i) * 3 * 60 * 60 * 1000),
      }));

      const result = await (service as any).predictNextFeeding(
        feedingActivities,
        mockChild,
      );

      expect(result.confidence).toBeGreaterThan(0.8);
      expect(result.reasoning).toContain('High confidence');
    });
  });

  describe('getAgeAppropriateWakeWindows', () => {
    it('should return wake windows for 0-1 month old', () => {
      const newborn = {
        ...mockChild,
        birthDate: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000), // 15 days old
      };

      const wakeWindows = (service as any).getAgeAppropriateWakeWindows(
        newborn,
      );

      expect(wakeWindows).toEqual([45, 60, 75]);
    });

    it('should return wake windows for 3-6 month old', () => {
      const infant = {
        ...mockChild,
        birthDate: new Date(Date.now() - 4 * 30 * 24 * 60 * 60 * 1000), // 4 months
      };

      const wakeWindows = (service as any).getAgeAppropriateWakeWindows(infant);

      expect(wakeWindows).toEqual([75, 90, 120]);
    });

    it('should return wake windows for 12+ month old', () => {
      const toddler = {
        ...mockChild,
        birthDate: new Date(Date.now() - 15 * 30 * 24 * 60 * 60 * 1000), // 15 months
      };

      const wakeWindows = (service as any).getAgeAppropriateWakeWindows(
        toddler,
      );

      expect(wakeWindows).toEqual([180, 240, 300]);
    });
  });

  describe('getDefaultFeedingInterval', () => {
    it('should return interval for newborn', () => {
      const newborn = {
        ...mockChild,
        birthDate: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000),
      };

      const interval = (service as any).getDefaultFeedingInterval(newborn);

      expect(interval).toBe(2.5);
    });

    it('should return interval for 3-6 month old', () => {
      const infant = {
        ...mockChild,
        birthDate: new Date(Date.now() - 4 * 30 * 24 * 60 * 60 * 1000),
      };

      const interval = (service as any).getDefaultFeedingInterval(infant);

      expect(interval).toBe(3.5);
    });

    it('should return interval for 6+ month old', () => {
      const older = {
        ...mockChild,
        birthDate: new Date(Date.now() - 8 * 30 * 24 * 60 * 60 * 1000),
      };

      const interval = (service as any).getDefaultFeedingInterval(older);

      expect(interval).toBe(4);
    });
  });

  describe('generateSleepReasoning', () => {
    it('should generate high confidence reasoning', () => {
      const reasoning = (service as any).generateSleepReasoning(
        90, // 1.5 hours wake window
        0.9, // 90% consistency
        20, // 20 data points
        0.88, // 88% confidence
      );

      expect(reasoning).toContain('High confidence');
      expect(reasoning).toContain('20 sleep sessions');
      expect(reasoning).toContain('1h 30m');
    });

    it('should generate moderate confidence reasoning', () => {
      const reasoning = (service as any).generateSleepReasoning(
        90,
        0.6,
        10,
        0.7,
      );

      expect(reasoning).toContain('Moderate confidence');
      expect(reasoning).toContain('10 sleep sessions');
    });

    it('should generate low confidence reasoning', () => {
      const reasoning = (service as any).generateSleepReasoning(
        90,
        0.4,
        5,
        0.5,
      );

      expect(reasoning).toContain('Low confidence');
      expect(reasoning).toContain('More data needed');
      expect(reasoning).toContain('5 sleep sessions');
    });
  });

  describe('generateFeedingReasoning', () => {
    it('should generate high confidence reasoning', () => {
      const reasoning = (service as any).generateFeedingReasoning(
        3.2, // 3.2 hours interval
        0.88,
        15,
        0.9,
      );

      expect(reasoning).toContain('High confidence');
      expect(reasoning).toContain('15 feedings');
      expect(reasoning).toContain('3.2 hours');
    });

    it('should generate moderate confidence reasoning', () => {
      const reasoning = (service as any).generateFeedingReasoning(
        3,
        0.7,
        8,
        0.65,
      );

      expect(reasoning).toContain('Moderate confidence');
      expect(reasoning).toContain('8 feedings');
    });

    it('should generate low confidence reasoning', () => {
      const reasoning = (service as any).generateFeedingReasoning(
        3,
        0.5,
        4,
        0.4,
      );

      expect(reasoning).toContain('Low confidence');
      expect(reasoning).toContain('4 feedings');
    });
  });

  describe('helper methods', () => {
    it('should calculate average time in minutes', () => {
      const dates = [
        new Date('2025-01-01T20:00:00'),
        new Date('2025-01-02T20:30:00'),
        new Date('2025-01-03T19:30:00'),
      ];

      const avgMinutes = (service as any).calculateAverageTimeInMinutes(dates);

      expect(avgMinutes).toBe(20 * 60); // 8PM = 1200 minutes
    });

    it('should calculate standard deviation', () => {
      const values = [10, 12, 14, 16, 18];

      const stdDev = (service as any).calculateStdDev(values);

      expect(stdDev).toBeCloseTo(2.83, 1);
    });

    it('should calculate age in months', () => {
      const birthDate = new Date(Date.now() - 6 * 30 * 24 * 60 * 60 * 1000);

      const ageInMonths = (service as any).calculateAgeInMonths(birthDate);

      expect(ageInMonths).toBeGreaterThanOrEqual(5);
      expect(ageInMonths).toBeLessThanOrEqual(7);
    });
  });
});
