import { Test, TestingModule } from '@nestjs/testing';
import { AdvancedPatternService } from './advanced-pattern.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Activity } from '../../database/entities/activity.entity';
import { Child } from '../../database/entities/child.entity';
import { ActivityType } from '../../database/entities/activity.entity';
import { Repository } from 'typeorm';

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

  const mockChild = {
    id: 'child123',
    name: 'Test Child',
    birthDate: new Date('2023-01-01'),
  };

  const generateMockActivities = (type: ActivityType, days: number): Activity[] => {
    const activities: Activity[] = [];
    const now = new Date();

    for (let d = 0; d < days; d++) {
      // Generate activities for each day
      const dayStart = new Date(now);
      dayStart.setDate(dayStart.getDate() - d);

      if (type === ActivityType.SLEEP) {
        // Night sleep
        const nightStart = new Date(dayStart.setHours(20, 0, 0, 0));
        const nightEnd = new Date(dayStart.setHours(23, 59, 0, 0));
        activities.push({
          id: `activity-${d}-night`,
          childId: 'child123',
          type: ActivityType.SLEEP,
          startedAt: nightStart,
          endedAt: nightEnd,
          loggedBy: 'user123',
          notes: 'Night sleep',
          metadata: {
            startTime: nightStart.toISOString(),
            endTime: nightEnd.toISOString(),
            duration: 239,
          },
          createdAt: nightStart,
          updatedAt: nightStart,
        } as unknown as Activity);

        // Morning nap
        if (Math.random() > 0.3) {
          const morningStart = new Date(dayStart.setHours(10, 0, 0, 0));
          const morningEnd = new Date(dayStart.setHours(11, 30, 0, 0));
          activities.push({
            id: `activity-${d}-morning`,
            childId: 'child123',
            type: ActivityType.SLEEP,
            startedAt: morningStart,
            endedAt: morningEnd,
            loggedBy: 'user123',
            notes: 'Morning nap',
            metadata: {
              startTime: morningStart.toISOString(),
              endTime: morningEnd.toISOString(),
              duration: 90,
            },
            createdAt: morningStart,
            updatedAt: morningStart,
          } as unknown as Activity);
        }

        // Afternoon nap
        if (Math.random() > 0.4) {
          const afternoonStart = new Date(dayStart.setHours(14, 0, 0, 0));
          const afternoonEnd = new Date(dayStart.setHours(15, 30, 0, 0));
          activities.push({
            id: `activity-${d}-afternoon`,
            childId: 'child123',
            type: ActivityType.SLEEP,
            startedAt: afternoonStart,
            endedAt: afternoonEnd,
            loggedBy: 'user123',
            notes: 'Afternoon nap',
            metadata: {
              startTime: afternoonStart.toISOString(),
              endTime: afternoonEnd.toISOString(),
              duration: 90,
            },
            createdAt: afternoonStart,
            updatedAt: afternoonStart,
          } as unknown as Activity);
        }
      } else if (type === ActivityType.FEEDING) {
        // Generate 6-8 feedings per day
        const feedingTimes = [6, 9, 12, 15, 18, 21];
        feedingTimes.forEach((hour, idx) => {
          const feedingStart = new Date(dayStart.setHours(hour, 0, 0, 0));
          const feedingEnd = new Date(dayStart.setHours(hour, 30, 0, 0));
          activities.push({
            id: `activity-${d}-feeding-${idx}`,
            childId: 'child123',
            type: ActivityType.FEEDING,
            startedAt: feedingStart,
            endedAt: feedingEnd,
            loggedBy: 'user123',
            notes: '',
            metadata: {
              startTime: feedingStart.toISOString(),
              endTime: feedingEnd.toISOString(),
              duration: 30,
              quantity: 120 + Math.random() * 60,
              feedingType: idx % 2 === 0 ? 'breast' : 'bottle',
            },
            createdAt: feedingStart,
            updatedAt: feedingStart,
          } as unknown as Activity);
        });
      }
    }

    return activities;
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AdvancedPatternService,
        {
          provide: getRepositoryToken(Activity),
          useValue: {
            createQueryBuilder: jest.fn(),
            find: jest.fn(),
          },
        },
        {
          provide: getRepositoryToken(Child),
          useValue: {
            findOne: jest.fn(),
          },
        },
      ],
    }).compile();

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

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

  describe('analyzeCircadianRhythm', () => {
    it('should analyze sleep patterns and detect circadian rhythm', async () => {
      const sleepActivities = generateMockActivities(ActivityType.SLEEP, 14);

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(sleepActivities);

      const result = await service.analyzeCircadianRhythm('child123', 14);

      expect(result).toHaveProperty('sleepPhaseShift');
      expect(result).toHaveProperty('consistency');
      expect(result).toHaveProperty('optimalBedtime');
      expect(result).toHaveProperty('optimalWakeTime');
      expect(result).toHaveProperty('chronotype');
      expect(result).toHaveProperty('melatoninOnset');
      expect(result).toHaveProperty('recommendedSchedule');
      expect(result.consistency).toBeGreaterThanOrEqual(0);
      expect(result.consistency).toBeLessThanOrEqual(1);
    });

    it('should throw error if insufficient data', async () => {
      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue([]);

      await expect(service.analyzeCircadianRhythm('invalid', 14))
        .rejects.toThrow('Insufficient sleep data for circadian rhythm analysis');
    });
  });

  describe('detectAnomalies', () => {
    it('should detect anomalies in activity patterns', async () => {
      const activities = [
        ...generateMockActivities(ActivityType.SLEEP, 30),
        ...generateMockActivities(ActivityType.FEEDING, 30),
      ];

      // Add an anomaly - very long sleep
      const anomalyStart = new Date();
      const anomalyEnd = new Date(Date.now() + 10 * 60 * 60 * 1000); // 10 hours
      activities.push({
        id: 'anomaly-1',
        childId: 'child123',
        type: ActivityType.SLEEP,
        startedAt: anomalyStart,
        endedAt: anomalyEnd,
        loggedBy: 'user123',
        notes: 'Unusually long sleep',
        metadata: {
          startTime: anomalyStart.toISOString(),
          endTime: anomalyEnd.toISOString(),
          duration: 600,
        },
        createdAt: anomalyStart,
        updatedAt: anomalyStart,
      } as unknown as Activity);

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(activities);

      const result = await service.detectAnomalies('child123', 30);

      expect(result).toHaveProperty('anomalies');
      expect(result).toHaveProperty('alerts');
      expect(result).toHaveProperty('confidenceScore');
      expect(Array.isArray(result.anomalies)).toBe(true);
      expect(Array.isArray(result.alerts)).toBe(true);
      expect(result.confidenceScore).toBeGreaterThanOrEqual(0);
      expect(result.confidenceScore).toBeLessThanOrEqual(1);
    });

    it('should return empty anomalies for no activities', async () => {
      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue([]);

      const result = await service.detectAnomalies('child123', 30);

      expect(result.anomalies).toHaveLength(0);
      expect(result.alerts).toHaveLength(0);
    });
  });

  describe('clusterActivities', () => {
    it('should cluster activities by patterns', async () => {
      const sleepActivities = generateMockActivities(ActivityType.SLEEP, 30);

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(sleepActivities);

      const result = await service.clusterActivities('child123', ActivityType.SLEEP, 30);

      expect(Array.isArray(result)).toBe(true);
      // Since we have sufficient activities (> 10), we should get clusters
      expect(result.length).toBeGreaterThan(0);

      if (result.length > 0) {
        expect(result[0]).toHaveProperty('clusterId');
        expect(result[0]).toHaveProperty('label');
        expect(result[0]).toHaveProperty('activities');
        expect(result[0]).toHaveProperty('centroid');
        expect(result[0]).toHaveProperty('confidence');
      }
    });

    it('should return empty array for insufficient data', async () => {
      const fewActivities = generateMockActivities(ActivityType.SLEEP, 2).slice(0, 5);

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(fewActivities);

      const result = await service.clusterActivities('child123', ActivityType.SLEEP, 30);

      expect(Array.isArray(result)).toBe(true);
      expect(result).toHaveLength(0);
    });
  });

  describe('analyzeCorrelations', () => {
    it('should analyze correlations between different activity types', async () => {
      const activities = [
        ...generateMockActivities(ActivityType.SLEEP, 14),
        ...generateMockActivities(ActivityType.FEEDING, 14),
      ];

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(activities);

      const result = await service.analyzeCorrelations('child123', 14);

      expect(result).toHaveProperty('feedingSleepCorrelation');
      expect(result).toHaveProperty('activityDiaperCorrelation');
      expect(result).toHaveProperty('insights');
      expect(typeof result.feedingSleepCorrelation).toBe('number');
      expect(Array.isArray(result.insights)).toBe(true);
    });

    it('should handle single activity type gracefully', async () => {
      const activities = generateMockActivities(ActivityType.SLEEP, 14);

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(activities);

      const result = await service.analyzeCorrelations('child123', 14);

      expect(result.feedingSleepCorrelation).toBe(0);
      expect(result.activityDiaperCorrelation).toBe(0);
    });
  });

  describe('analyzeTrends', () => {
    it('should analyze trends for activity type', async () => {
      const feedingActivities = generateMockActivities(ActivityType.FEEDING, 30);

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(feedingActivities);

      const result = await service.analyzeTrends('child123', ActivityType.FEEDING);

      expect(result).toHaveProperty('shortTermTrend');
      expect(result).toHaveProperty('mediumTermTrend');
      expect(result).toHaveProperty('longTermTrend');
      expect(result).toHaveProperty('prediction');
      expect(['improving', 'stable', 'declining']).toContain(result.shortTermTrend.direction);
    });

    it('should throw error for insufficient data', async () => {
      const fewActivities = generateMockActivities(ActivityType.FEEDING, 2).slice(0, 5);

      jest.spyOn(childRepository, 'findOne').mockResolvedValue(mockChild as Child);
      jest.spyOn(activityRepository, 'find').mockResolvedValue(fewActivities);

      await expect(service.analyzeTrends('child123', ActivityType.FEEDING))
        .rejects.toThrow('Insufficient data for trend analysis');
    });
  });
});