import { DailyActiveStats } from '../../../../src/domain/aggregates/daily-active-stats/daily-active-stats.aggregate';
import { DauCalculatedEvent } from '../../../../src/domain/events/dau-calculated.event';

describe('DailyActiveStats Aggregate', () => {
  describe('create', () => {
    it('should create DailyActiveStats with required properties', () => {
      const day = new Date('2025-01-01');
      const stats = DailyActiveStats.create({
        day,
        dauCount: 1000,
      });

      expect(stats.day).toEqual(day);
      expect(stats.dauCount).toBe(1000);
      expect(stats.dauByProvince).toBeInstanceOf(Map);
      expect(stats.dauByProvince.size).toBe(0);
      expect(stats.dauByCity).toBeInstanceOf(Map);
      expect(stats.dauByCity.size).toBe(0);
      expect(stats.calculatedAt).toBeInstanceOf(Date);
      expect(stats.version).toBe(1);
    });

    it('should create DailyActiveStats with province breakdown', () => {
      const byProvince = new Map<string, number>([
        ['Shanghai', 500],
        ['Beijing', 300],
        ['Guangdong', 200],
      ]);

      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 1000,
        dauByProvince: byProvince,
      });

      expect(stats.dauByProvince.size).toBe(3);
      expect(stats.dauByProvince.get('Shanghai')).toBe(500);
      expect(stats.dauByProvince.get('Beijing')).toBe(300);
    });

    it('should create DailyActiveStats with city breakdown', () => {
      const byCity = new Map<string, number>([
        ['Shanghai', 500],
        ['Shenzhen', 200],
      ]);

      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 700,
        dauByCity: byCity,
      });

      expect(stats.dauByCity.size).toBe(2);
      expect(stats.dauByCity.get('Shanghai')).toBe(500);
    });

    it('should apply DauCalculatedEvent on create', () => {
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 1000,
      });

      const events = stats.getUncommittedEvents();
      expect(events.length).toBe(1);
      expect(events[0]).toBeInstanceOf(DauCalculatedEvent);
    });

    it('should handle zero DAU count', () => {
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 0,
      });

      expect(stats.dauCount).toBe(0);
    });
  });

  describe('recalculate', () => {
    it('should update DAU count', () => {
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 1000,
      });

      stats.commit(); // Clear initial events
      const oldCalculatedAt = stats.calculatedAt;

      // Wait a tiny bit to ensure calculatedAt changes
      stats.recalculate(1500);

      expect(stats.dauCount).toBe(1500);
      expect(stats.version).toBe(2);
    });

    it('should update province breakdown', () => {
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 1000,
      });

      const newByProvince = new Map<string, number>([
        ['Shanghai', 800],
        ['Beijing', 700],
      ]);

      stats.recalculate(1500, newByProvince);

      expect(stats.dauByProvince.get('Shanghai')).toBe(800);
      expect(stats.dauByProvince.get('Beijing')).toBe(700);
    });

    it('should update city breakdown', () => {
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 1000,
      });

      const newByCity = new Map<string, number>([
        ['Shenzhen', 300],
      ]);

      stats.recalculate(1500, undefined, newByCity);

      expect(stats.dauByCity.get('Shenzhen')).toBe(300);
    });

    it('should increment version', () => {
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 1000,
      });

      expect(stats.version).toBe(1);

      stats.recalculate(1100);
      expect(stats.version).toBe(2);

      stats.recalculate(1200);
      expect(stats.version).toBe(3);
    });

    it('should apply DauCalculatedEvent on recalculate', () => {
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 1000,
      });

      stats.commit(); // Clear create event
      stats.recalculate(1500);

      const events = stats.getUncommittedEvents();
      expect(events.length).toBe(1);
      expect(events[0]).toBeInstanceOf(DauCalculatedEvent);
    });
  });

  describe('reconstitute', () => {
    it('should reconstitute from persistence data', () => {
      const day = new Date('2025-01-01');
      const calculatedAt = new Date('2025-01-01T23:00:00.000Z');
      const byProvince = new Map<string, number>([['Shanghai', 500]]);
      const byCity = new Map<string, number>([['Shanghai', 500]]);

      const stats = DailyActiveStats.reconstitute({
        day,
        dauCount: 2000,
        dauByProvince: byProvince,
        dauByCity: byCity,
        calculatedAt,
        version: 5,
      });

      expect(stats.day).toEqual(day);
      expect(stats.dauCount).toBe(2000);
      expect(stats.dauByProvince.get('Shanghai')).toBe(500);
      expect(stats.dauByCity.get('Shanghai')).toBe(500);
      expect(stats.calculatedAt).toEqual(calculatedAt);
      expect(stats.version).toBe(5);
    });

    it('should not apply events on reconstitute', () => {
      const stats = DailyActiveStats.reconstitute({
        day: new Date('2025-01-01'),
        dauCount: 2000,
        dauByProvince: new Map(),
        dauByCity: new Map(),
        calculatedAt: new Date(),
        version: 1,
      });

      const events = stats.getUncommittedEvents();
      expect(events.length).toBe(0);
    });
  });

  describe('getters return copies', () => {
    it('should return a copy of dauByProvince', () => {
      const byProvince = new Map<string, number>([['Shanghai', 500]]);
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 500,
        dauByProvince: byProvince,
      });

      const returned = stats.dauByProvince;
      returned.set('NewProvince', 100);

      // Original should not be modified
      expect(stats.dauByProvince.has('NewProvince')).toBe(false);
    });

    it('should return a copy of dauByCity', () => {
      const byCity = new Map<string, number>([['Shenzhen', 300]]);
      const stats = DailyActiveStats.create({
        day: new Date('2025-01-01'),
        dauCount: 300,
        dauByCity: byCity,
      });

      const returned = stats.dauByCity;
      returned.set('NewCity', 50);

      // Original should not be modified
      expect(stats.dauByCity.has('NewCity')).toBe(false);
    });
  });
});
