import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository, Between } from 'typeorm';
import {
  PatternAnalysisService,
  SleepPattern,
  FeedingPattern,
  DiaperPattern,
} from './pattern-analysis.service';
import { Activity, ActivityType } from '../../database/entities/activity.entity';
import { Child } from '../../database/entities/child.entity';

describe('PatternAnalysisService', () => {
  let service: PatternAnalysisService;
  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(),
    findOne: jest.fn(),
  };

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

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

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

    jest.clearAllMocks();
  });

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

  describe('analyzePatterns', () => {
    it('should analyze all patterns for a child', async () => {
      const sleepActivities = [
        {
          id: '1',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T06:00:00'),
        },
        {
          id: '2',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-02T13:00:00'),
          endedAt: new Date('2025-01-02T15:00:00'),
        },
        {
          id: '3',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-02T20:30:00'),
          endedAt: new Date('2025-01-03T07:00:00'),
        },
      ];

      const feedingActivities = [
        {
          id: '4',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T08:00:00'),
          metadata: { method: 'bottle' },
        },
        {
          id: '5',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T11:00:00'),
          metadata: { method: 'nursing' },
        },
        {
          id: '6',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T14:00:00'),
          metadata: { method: 'bottle' },
        },
      ];

      const diaperActivities = [
        {
          id: '7',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T09:00:00'),
          metadata: { type: 'wet' },
        },
        {
          id: '8',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T12:00:00'),
          metadata: { type: 'dirty' },
        },
        {
          id: '9',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T15:00:00'),
          metadata: { type: 'both' },
        },
        {
          id: '10',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T18:00:00'),
          metadata: { type: 'wet' },
        },
      ];

      const allActivities = [
        ...sleepActivities,
        ...feedingActivities,
        ...diaperActivities,
      ];

      mockActivityRepository.find.mockResolvedValue(allActivities);
      mockChildRepository.findOne.mockResolvedValue(mockChild);

      const result = await service.analyzePatterns('child_123', 7);

      expect(result).toHaveProperty('sleep');
      expect(result).toHaveProperty('feeding');
      expect(result).toHaveProperty('diaper');
      expect(result).toHaveProperty('recommendations');
      expect(result).toHaveProperty('concernsDetected');
      expect(Array.isArray(result.recommendations)).toBe(true);
      expect(Array.isArray(result.concernsDetected)).toBe(true);
    });

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

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

    it('should use custom day range', async () => {
      mockActivityRepository.find.mockResolvedValue([]);
      mockChildRepository.findOne.mockResolvedValue(mockChild);

      await service.analyzePatterns('child_123', 14);

      expect(activityRepository.find).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            childId: 'child_123',
            startedAt: expect.any(Object), // Between query
          }),
        }),
      );
    });
  });

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

      const result = await service.analyzeSleepPatterns(activities, mockChild);

      expect(result).toBeNull();
    });

    it('should calculate sleep pattern statistics', async () => {
      const sleepActivities = [
        {
          id: '1',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T06:00:00'), // 10 hours
        },
        {
          id: '2',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-02T13:00:00'),
          endedAt: new Date('2025-01-02T15:00:00'), // 2 hours nap
        },
        {
          id: '3',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-02T20:30:00'),
          endedAt: new Date('2025-01-03T07:00:00'), // 10.5 hours
        },
      ];

      const result = await service.analyzeSleepPatterns(
        sleepActivities,
        mockChild,
      );

      expect(result).not.toBeNull();
      expect(result!.averageDuration).toBeGreaterThan(0);
      expect(result!.averageBedtime).toMatch(/\d{2}:\d{2}/);
      expect(result!.averageWakeTime).toMatch(/\d{2}:\d{2}/);
      expect(result!.nightWakings).toBeGreaterThanOrEqual(0);
      expect(result!.napCount).toBeGreaterThanOrEqual(0);
      expect(result!.consistency).toBeGreaterThanOrEqual(0);
      expect(result!.consistency).toBeLessThanOrEqual(1);
      expect(['improving', 'stable', 'declining']).toContain(result!.trend);
    });

    it('should detect improving sleep trend', async () => {
      const sleepActivities = [
        {
          id: '1',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T04:00:00'), // 8 hours
        },
        {
          id: '2',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-02T20:00:00'),
          endedAt: new Date('2025-01-03T05:00:00'), // 9 hours
        },
        {
          id: '3',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-03T20:00:00'),
          endedAt: new Date('2025-01-04T07:00:00'), // 11 hours
        },
      ];

      const result = await service.analyzeSleepPatterns(
        sleepActivities,
        mockChild,
      );

      expect(result!.trend).toBe('improving');
    });

    it('should detect declining sleep trend', async () => {
      const sleepActivities = [
        {
          id: '1',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T07:00:00'), // 11 hours
        },
        {
          id: '2',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-02T20:00:00'),
          endedAt: new Date('2025-01-03T05:00:00'), // 9 hours
        },
        {
          id: '3',
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-03T20:00:00'),
          endedAt: new Date('2025-01-04T04:00:00'), // 8 hours
        },
      ];

      const result = await service.analyzeSleepPatterns(
        sleepActivities,
        mockChild,
      );

      expect(result!.trend).toBe('declining');
    });
  });

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

      const result = await service.analyzeFeedingPatterns(activities, mockChild);

      expect(result).toBeNull();
    });

    it('should calculate feeding pattern statistics', async () => {
      const feedingActivities = [
        {
          id: '1',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T08:00:00'),
          endedAt: new Date('2025-01-01T08:20:00'),
          metadata: { method: 'bottle' },
        },
        {
          id: '2',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T11:00:00'),
          endedAt: new Date('2025-01-01T11:15:00'),
          metadata: { method: 'nursing' },
        },
        {
          id: '3',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T14:00:00'),
          endedAt: new Date('2025-01-01T14:20:00'),
          metadata: { method: 'bottle' },
        },
      ];

      const result = await service.analyzeFeedingPatterns(
        feedingActivities,
        mockChild,
      );

      expect(result).not.toBeNull();
      expect(result!.averageInterval).toBeGreaterThan(0);
      expect(result!.averageDuration).toBeGreaterThan(0);
      expect(result!.totalFeedings).toBe(3);
      expect(result!.feedingMethod).toHaveProperty('bottle');
      expect(result!.feedingMethod).toHaveProperty('nursing');
      expect(result!.consistency).toBeGreaterThanOrEqual(0);
      expect(['increasing', 'stable', 'decreasing']).toContain(result!.trend);
    });

    it('should handle feedings without duration', async () => {
      const feedingActivities = [
        {
          id: '1',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T08:00:00'),
          metadata: { method: 'bottle' },
        },
        {
          id: '2',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T11:00:00'),
          metadata: { method: 'bottle' },
        },
        {
          id: '3',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T14:00:00'),
          metadata: { method: 'bottle' },
        },
      ];

      const result = await service.analyzeFeedingPatterns(
        feedingActivities,
        mockChild,
      );

      expect(result!.averageDuration).toBe(0);
    });

    it('should track feeding methods', async () => {
      const feedingActivities = [
        {
          id: '1',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T08:00:00'),
          metadata: { method: 'bottle' },
        },
        {
          id: '2',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T11:00:00'),
          metadata: { method: 'bottle' },
        },
        {
          id: '3',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T14:00:00'),
          metadata: { method: 'nursing' },
        },
      ];

      const result = await service.analyzeFeedingPatterns(
        feedingActivities,
        mockChild,
      );

      expect(result!.feedingMethod.bottle).toBe(2);
      expect(result!.feedingMethod.nursing).toBe(1);
    });
  });

  describe('analyzeDiaperPatterns', () => {
    it('should return null with insufficient data', async () => {
      const activities = [
        {
          id: '1',
          type: ActivityType.DIAPER,
          startedAt: new Date(),
          metadata: { type: 'wet' },
        },
      ];

      const result = await service.analyzeDiaperPatterns(activities, mockChild);

      expect(result).toBeNull();
    });

    it('should calculate diaper pattern statistics', async () => {
      const diaperActivities = [
        {
          id: '1',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T08:00:00'),
          metadata: { type: 'wet' },
        },
        {
          id: '2',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T11:00:00'),
          metadata: { type: 'dirty' },
        },
        {
          id: '3',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T14:00:00'),
          metadata: { type: 'both' },
        },
        {
          id: '4',
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T17:00:00'),
          metadata: { type: 'wet' },
        },
      ];

      const result = await service.analyzeDiaperPatterns(
        diaperActivities,
        mockChild,
      );

      expect(result).not.toBeNull();
      expect(result!.wetDiapersPerDay).toBeGreaterThan(0);
      expect(result!.dirtyDiapersPerDay).toBeGreaterThan(0);
      expect(result!.averageInterval).toBeGreaterThan(0);
      expect(typeof result!.isHealthy).toBe('boolean');
      expect(typeof result!.notes).toBe('string');
    });

    it('should detect healthy diaper pattern', async () => {
      const diaperActivities = [
        {
          id: '1',
          type: ActivityType.DIAPER,
          startedAt: new Date(Date.now() - 12 * 60 * 60 * 1000),
          metadata: { type: 'wet' },
        },
        {
          id: '2',
          type: ActivityType.DIAPER,
          startedAt: new Date(Date.now() - 9 * 60 * 60 * 1000),
          metadata: { type: 'both' },
        },
        {
          id: '3',
          type: ActivityType.DIAPER,
          startedAt: new Date(Date.now() - 6 * 60 * 60 * 1000),
          metadata: { type: 'wet' },
        },
        {
          id: '4',
          type: ActivityType.DIAPER,
          startedAt: new Date(Date.now() - 3 * 60 * 60 * 1000),
          metadata: { type: 'wet' },
        },
        {
          id: '5',
          type: ActivityType.DIAPER,
          startedAt: new Date(),
          metadata: { type: 'dirty' },
        },
      ];

      const result = await service.analyzeDiaperPatterns(
        diaperActivities,
        mockChild,
      );

      expect(result!.isHealthy).toBe(true);
      expect(result!.notes).toContain('healthy');
    });

    it('should detect unhealthy diaper pattern', async () => {
      const diaperActivities = [
        {
          id: '1',
          type: ActivityType.DIAPER,
          startedAt: new Date(Date.now() - 24 * 60 * 60 * 1000),
          metadata: { type: 'wet' },
        },
        {
          id: '2',
          type: ActivityType.DIAPER,
          startedAt: new Date(Date.now() - 12 * 60 * 60 * 1000),
          metadata: { type: 'wet' },
        },
        {
          id: '3',
          type: ActivityType.DIAPER,
          startedAt: new Date(),
          metadata: { type: 'wet' },
        },
      ];

      const result = await service.analyzeDiaperPatterns(
        diaperActivities,
        mockChild,
      );

      expect(result!.isHealthy).toBe(false);
      expect(result!.notes).toContain('below expected');
    });
  });

  describe('generateRecommendations', () => {
    it('should generate sleep recommendations for low consistency', () => {
      const sleepPattern: SleepPattern = {
        averageDuration: 600,
        averageBedtime: '20:00',
        averageWakeTime: '06:00',
        nightWakings: 2,
        napCount: 2,
        consistency: 0.5,
        trend: 'stable',
      };

      const recommendations = (service as any).generateRecommendations(
        sleepPattern,
        null,
        null,
        mockChild,
      );

      expect(recommendations).toContain(
        expect.stringContaining('consistent bedtime routine'),
      );
    });

    it('should generate recommendations for frequent night wakings', () => {
      const sleepPattern: SleepPattern = {
        averageDuration: 600,
        averageBedtime: '20:00',
        averageWakeTime: '06:00',
        nightWakings: 5,
        napCount: 2,
        consistency: 0.8,
        trend: 'stable',
      };

      const recommendations = (service as any).generateRecommendations(
        sleepPattern,
        null,
        null,
        mockChild,
      );

      expect(recommendations).toContain(
        expect.stringContaining('reduce night wakings'),
      );
    });

    it('should generate recommendations for low sleep duration', () => {
      const sleepPattern: SleepPattern = {
        averageDuration: 480, // 8 hours
        averageBedtime: '20:00',
        averageWakeTime: '04:00',
        nightWakings: 1,
        napCount: 1,
        consistency: 0.8,
        trend: 'stable',
      };

      const recommendations = (service as any).generateRecommendations(
        sleepPattern,
        null,
        null,
        mockChild,
      );

      expect(recommendations).toContain(
        expect.stringContaining('earlier bedtimes or longer naps'),
      );
    });

    it('should generate feeding recommendations', () => {
      const feedingPattern: FeedingPattern = {
        averageInterval: 3,
        averageDuration: 15,
        totalFeedings: 8,
        feedingMethod: { bottle: 8 },
        consistency: 0.5,
        trend: 'decreasing',
      };

      const recommendations = (service as any).generateRecommendations(
        null,
        feedingPattern,
        null,
        mockChild,
      );

      expect(recommendations.length).toBeGreaterThan(0);
      expect(recommendations).toContain(
        expect.stringMatching(/regular schedule|decline/),
      );
    });

    it('should generate diaper recommendations for unhealthy pattern', () => {
      const diaperPattern: DiaperPattern = {
        wetDiapersPerDay: 2,
        dirtyDiapersPerDay: 0.5,
        averageInterval: 6,
        isHealthy: false,
        notes: 'Low output',
      };

      const recommendations = (service as any).generateRecommendations(
        null,
        null,
        diaperPattern,
        mockChild,
      );

      expect(recommendations).toContain(
        expect.stringContaining('adequate hydration'),
      );
    });
  });

  describe('detectConcerns', () => {
    it('should detect declining sleep trend', () => {
      const sleepPattern: SleepPattern = {
        averageDuration: 600,
        averageBedtime: '20:00',
        averageWakeTime: '06:00',
        nightWakings: 2,
        napCount: 2,
        consistency: 0.8,
        trend: 'declining',
      };

      const concerns = (service as any).detectConcerns(
        sleepPattern,
        null,
        null,
        mockChild,
      );

      expect(concerns).toContain(
        expect.stringContaining('Sleep duration has been decreasing'),
      );
    });

    it('should detect frequent night wakings concern', () => {
      const sleepPattern: SleepPattern = {
        averageDuration: 600,
        averageBedtime: '20:00',
        averageWakeTime: '06:00',
        nightWakings: 6,
        napCount: 2,
        consistency: 0.8,
        trend: 'stable',
      };

      const concerns = (service as any).detectConcerns(
        sleepPattern,
        null,
        null,
        mockChild,
      );

      expect(concerns).toContain(
        expect.stringContaining('Frequent night wakings'),
      );
    });

    it('should detect feeding concerns', () => {
      const feedingPattern: FeedingPattern = {
        averageInterval: 4,
        averageDuration: 15,
        totalFeedings: 10,
        feedingMethod: { bottle: 10 },
        consistency: 0.7,
        trend: 'decreasing',
      };

      const concerns = (service as any).detectConcerns(
        null,
        feedingPattern,
        null,
        mockChild,
      );

      expect(concerns).toContain(
        expect.stringContaining('Feeding frequency appears to be decreasing'),
      );
    });

    it('should detect diaper concerns', () => {
      const diaperPattern: DiaperPattern = {
        wetDiapersPerDay: 2,
        dirtyDiapersPerDay: 0.5,
        averageInterval: 8,
        isHealthy: false,
        notes: 'Low',
      };

      const concerns = (service as any).detectConcerns(
        null,
        null,
        diaperPattern,
        mockChild,
      );

      expect(concerns).toContain(
        expect.stringContaining('Diaper output is below expected'),
      );
    });
  });

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

      const avgTime = (service as any).calculateAverageTime(dates);

      expect(avgTime).toMatch(/\d{2}:\d{2}/);
      expect(avgTime).toBe('20:00');
    });

    it('should return 00:00 for empty date array', () => {
      const avgTime = (service as any).calculateAverageTime([]);

      expect(avgTime).toBe('00:00');
    });

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

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

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

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

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

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