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

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

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

  const mockActivities = [
    {
      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.FEEDING,
      startedAt: new Date('2025-01-01T08:00:00'),
      metadata: { method: 'bottle' },
    },
    {
      id: '3',
      type: ActivityType.DIAPER,
      startedAt: new Date('2025-01-01T09:00:00'),
      metadata: { type: 'wet' },
    },
  ];

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

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

  const mockPatternAnalysisService = {
    analyzePatterns: jest.fn().mockResolvedValue({
      sleep: { averageDuration: 600, trend: 'stable' },
      feeding: { averageInterval: 3, trend: 'stable' },
      diaper: { wetDiapersPerDay: 6, isHealthy: true },
      recommendations: [],
      concernsDetected: [],
    }),
  };

  const mockPredictionService = {
    generatePredictions: jest.fn().mockResolvedValue({
      sleep: { nextNapTime: new Date(), nextNapConfidence: 0.85 },
      feeding: { nextFeedingTime: new Date(), confidence: 0.9 },
      generatedAt: new Date(),
    }),
  };

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

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

    jest.clearAllMocks();
  });

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

  describe('generateWeeklyReport', () => {
    it('should generate weekly report for a child', async () => {
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

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

      expect(result).toHaveProperty('childId', 'child_123');
      expect(result).toHaveProperty('childName', 'Baby Jane');
      expect(result).toHaveProperty('weekStart');
      expect(result).toHaveProperty('weekEnd');
      expect(result).toHaveProperty('summary');
      expect(result).toHaveProperty('patterns');
      expect(result).toHaveProperty('predictions');
      expect(result).toHaveProperty('highlights');
      expect(result).toHaveProperty('concerns');
    });

    it('should use custom start date when provided', async () => {
      const customStart = new Date('2025-01-01');
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

      const result = await service.generateWeeklyReport('child_123', customStart);

      expect(result.weekStart).toEqual(customStart);
      expect(activityRepository.find).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            childId: 'child_123',
            startedAt: expect.any(Object),
          }),
        }),
      );
    });

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

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

    it('should include patterns and predictions', async () => {
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

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

      expect(patternAnalysisService.analyzePatterns).toHaveBeenCalledWith(
        'child_123',
        7,
      );
      expect(predictionService.generatePredictions).toHaveBeenCalledWith(
        'child_123',
      );
      expect(result.patterns).toBeDefined();
      expect(result.predictions).toBeDefined();
    });
  });

  describe('generateMonthlyReport', () => {
    it('should generate monthly report for a child', async () => {
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

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

      expect(result).toHaveProperty('childId', 'child_123');
      expect(result).toHaveProperty('childName', 'Baby Jane');
      expect(result).toHaveProperty('month');
      expect(result).toHaveProperty('summary');
      expect(result).toHaveProperty('trends');
      expect(result).toHaveProperty('weeklyBreakdown');
      expect(result.month).toMatch(/\d{4}-\d{2}/);
    });

    it('should use custom month when provided', async () => {
      const customMonth = new Date('2024-06-15');
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

      const result = await service.generateMonthlyReport(
        'child_123',
        customMonth,
      );

      expect(result.month).toBe('2024-06');
    });

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

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

  describe('exportData', () => {
    it('should export data in JSON format', async () => {
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

      const result = await service.exportData('child_123', null, null, 'json');

      expect(result.format).toBe('json');
      expect(result.data).toEqual(mockActivities);
      expect(result.contentType).toBe('application/json');
      expect(result.generatedAt).toBeInstanceOf(Date);
    });

    it('should export data in CSV format', async () => {
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

      const result = await service.exportData('child_123', null, null, 'csv');

      expect(result.format).toBe('csv');
      expect(result.contentType).toBe('text/csv');
      expect(typeof result.data).toBe('string');
    });

    it('should use custom date range', async () => {
      const start = new Date('2025-01-01');
      const end = new Date('2025-01-31');
      mockChildRepository.findOne.mockResolvedValue(mockChild);
      mockActivityRepository.find.mockResolvedValue(mockActivities);

      await service.exportData('child_123', start, end, 'json');

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

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

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

  describe('calculateWeeklySummary', () => {
    it('should calculate weekly summary statistics', () => {
      const activities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T06:00:00'), // 10 hours
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-02T13:00:00'),
          endedAt: new Date('2025-01-02T15:00:00'), // 2 hours nap
        },
        {
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T08:00:00'),
        },
        {
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T11:00:00'),
        },
        {
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-01T09:00:00'),
          metadata: { type: 'wet' },
        },
      ];

      const result = (service as any).calculateWeeklySummary(activities);

      expect(result.totalSleep).toBeGreaterThan(0);
      expect(result.totalFeedings).toBe(2);
      expect(result.totalDiaperChanges).toBe(1);
      expect(result.averageSleepPerNight).toBeGreaterThan(0);
      expect(result.averageNapsPerDay).toBeGreaterThanOrEqual(0);
    });
  });

  describe('calculateMonthlySummary', () => {
    it('should calculate monthly summary statistics', () => {
      const activities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T06:00:00'),
        },
        {
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-15T08:00:00'),
        },
        {
          type: ActivityType.DIAPER,
          startedAt: new Date('2025-01-20T09:00:00'),
          metadata: { type: 'wet' },
        },
      ];

      const result = (service as any).calculateMonthlySummary(activities);

      expect(result.totalSleep).toBeGreaterThan(0);
      expect(result.totalFeedings).toBe(1);
      expect(result.totalDiaperChanges).toBe(1);
      expect(result.weeklyAverages).toHaveProperty('sleep');
      expect(result.weeklyAverages).toHaveProperty('feedings');
      expect(result.weeklyAverages).toHaveProperty('diapers');
    });
  });

  describe('analyzeTrends', () => {
    it('should detect improving sleep trend', () => {
      const activities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T04:00:00'), // 8 hours
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-15T20:00:00'),
          endedAt: new Date('2025-01-16T07:00:00'), // 11 hours
        },
      ];

      const result = (service as any).analyzeTrends(activities);

      expect(result.sleepTrend).toBe('improving');
    });

    it('should detect declining sleep trend', () => {
      const activities = [
        {
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-01T20:00:00'),
          endedAt: new Date('2025-01-02T07:00:00'), // 11 hours
        },
        {
          type: ActivityType.SLEEP,
          startedAt: new Date('2025-01-15T20:00:00'),
          endedAt: new Date('2025-01-16T04:00:00'), // 8 hours
        },
      ];

      const result = (service as any).analyzeTrends(activities);

      expect(result.sleepTrend).toBe('declining');
    });

    it('should detect increasing feeding trend', () => {
      const activities = [
        { type: ActivityType.FEEDING, startedAt: new Date('2025-01-01T08:00:00') },
        { type: ActivityType.FEEDING, startedAt: new Date('2025-01-15T08:00:00') },
        { type: ActivityType.FEEDING, startedAt: new Date('2025-01-16T08:00:00') },
        { type: ActivityType.FEEDING, startedAt: new Date('2025-01-17T08:00:00') },
        { type: ActivityType.FEEDING, startedAt: new Date('2025-01-18T08:00:00') },
      ];

      const result = (service as any).analyzeTrends(activities);

      expect(result.feedingTrend).toBe('increasing');
    });

    it('should count milestone activities', () => {
      const activities = [
        { type: ActivityType.MILESTONE, startedAt: new Date('2025-01-01') },
        { type: ActivityType.MILESTONE, startedAt: new Date('2025-01-15') },
      ];

      const result = (service as any).analyzeTrends(activities);

      expect(result.growthMilestones).toBe(2);
    });
  });

  describe('convertToCSV', () => {
    it('should convert activities to CSV format', () => {
      const activities = [
        {
          id: '1',
          type: ActivityType.FEEDING,
          startedAt: new Date('2025-01-01T08:00:00'),
          endedAt: null,
          metadata: { method: 'bottle', amount: 120 },
        },
      ];

      const csv = (service as any).convertToCSV(activities);

      expect(typeof csv).toBe('string');
      expect(csv).toContain('id,type,startedAt');
      expect(csv).toContain('FEEDING');
    });

    it('should handle empty activities array', () => {
      const csv = (service as any).convertToCSV([]);

      expect(typeof csv).toBe('string');
      expect(csv).toContain('id,type,startedAt');
    });
  });

  describe('generateHighlights', () => {
    it('should generate highlights from summary and patterns', () => {
      const summary = {
        totalSleep: 5000,
        totalFeedings: 56,
        totalDiaperChanges: 42,
        averageSleepPerNight: 600,
        averageNapsPerDay: 2.5,
      };

      const patterns = {
        sleep: { averageDuration: 600, trend: 'improving' },
        feeding: { averageInterval: 3, trend: 'stable' },
        diaper: { wetDiapersPerDay: 6, isHealthy: true },
        recommendations: [],
        concernsDetected: [],
      };

      const highlights = (service as any).generateHighlights(summary, patterns);

      expect(Array.isArray(highlights)).toBe(true);
      expect(highlights.length).toBeGreaterThan(0);
    });
  });
});
