import { TeamRatingEvent } from './TeamRatingEvent';
import { TeamRatingEventId } from '../value-objects/TeamRatingEventId';
import { TeamRatingDimensionKey } from '../value-objects/TeamRatingDimensionKey';
import { TeamRatingDelta } from '../value-objects/TeamRatingDelta';
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';

describe('TeamRatingEvent', () => {
  const validProps = {
    id: TeamRatingEventId.create('123e4567-e89b-12d3-a456-426614174000'),
    teamId: 'team-123',
    dimension: TeamRatingDimensionKey.create('driving'),
    delta: TeamRatingDelta.create(10),
    occurredAt: new Date('2024-01-01T00:00:00Z'),
    createdAt: new Date('2024-01-01T00:00:00Z'),
    source: { type: 'race' as const, id: 'race-456' },
    reason: { code: 'RACE_FINISH', description: 'Finished 1st in race' },
    visibility: { public: true },
    version: 1,
  };

  describe('create', () => {
    it('should create a valid rating event', () => {
      const event = TeamRatingEvent.create(validProps);

      expect(event.id.value).toBe(validProps.id.value);
      expect(event.teamId).toBe(validProps.teamId);
      expect(event.dimension.value).toBe('driving');
      expect(event.delta.value).toBe(10);
      expect(event.occurredAt).toEqual(validProps.occurredAt);
      expect(event.createdAt).toEqual(validProps.createdAt);
      expect(event.source).toEqual(validProps.source);
      expect(event.reason).toEqual(validProps.reason);
      expect(event.visibility).toEqual(validProps.visibility);
      expect(event.version).toBe(1);
    });

    it('should create event with optional weight', () => {
      const props = { ...validProps, weight: 2 };
      const event = TeamRatingEvent.create(props);

      expect(event.weight).toBe(2);
    });

    it('should throw for empty teamId', () => {
      const props = { ...validProps, teamId: '' };
      expect(() => TeamRatingEvent.create(props)).toThrow(RacingDomainValidationError);
    });

    it('should throw for missing dimension', () => {
      const { dimension: _dimension, ...rest } = validProps;
      expect(() => TeamRatingEvent.create(rest as typeof validProps)).toThrow(RacingDomainValidationError);
    });

    it('should throw for missing delta', () => {
      const { delta: _delta, ...rest } = validProps;
      expect(() => TeamRatingEvent.create(rest as typeof validProps)).toThrow(RacingDomainValidationError);
    });

    it('should throw for missing source', () => {
      const { source: _source, ...rest } = validProps;
      expect(() => TeamRatingEvent.create(rest as typeof validProps)).toThrow(RacingDomainValidationError);
    });

    it('should throw for missing reason', () => {
      const { reason: _reason, ...rest } = validProps;
      expect(() => TeamRatingEvent.create(rest as typeof validProps)).toThrow(RacingDomainValidationError);
    });

    it('should throw for missing visibility', () => {
      const { visibility: _visibility, ...rest } = validProps;
      expect(() => TeamRatingEvent.create(rest as typeof validProps)).toThrow(RacingDomainValidationError);
    });

    it('should throw for invalid weight', () => {
      const props = { ...validProps, weight: 0 };
      expect(() => TeamRatingEvent.create(props)).toThrow(RacingDomainValidationError);
    });

    it('should throw for future occurredAt', () => {
      const futureDate = new Date(Date.now() + 86400000); // Tomorrow
      const props = { ...validProps, occurredAt: futureDate };
      expect(() => TeamRatingEvent.create(props)).toThrow(RacingDomainValidationError);
    });

    it('should throw for future createdAt', () => {
      const futureDate = new Date(Date.now() + 86400000); // Tomorrow
      const props = { ...validProps, createdAt: futureDate };
      expect(() => TeamRatingEvent.create(props)).toThrow(RacingDomainValidationError);
    });

    it('should throw for version < 1', () => {
      const props = { ...validProps, version: 0 };
      expect(() => TeamRatingEvent.create(props)).toThrow(RacingDomainValidationError);
    });

    it('should throw for adminTrust dimension with race source', () => {
      const props = {
        ...validProps,
        dimension: TeamRatingDimensionKey.create('adminTrust'),
        source: { type: 'race' as const, id: 'race-456' },
      };
      expect(() => TeamRatingEvent.create(props)).toThrow(RacingDomainInvariantError);
    });

    it('should throw for driving dimension with vote source', () => {
      const props = {
        ...validProps,
        dimension: TeamRatingDimensionKey.create('driving'),
        source: { type: 'vote' as const, id: 'vote-456' },
      };
      expect(() => TeamRatingEvent.create(props)).toThrow(RacingDomainInvariantError);
    });

    it('should allow adminTrust with adminAction source', () => {
      const props = {
        ...validProps,
        dimension: TeamRatingDimensionKey.create('adminTrust'),
        source: { type: 'adminAction' as const, id: 'action-456' },
      };
      const event = TeamRatingEvent.create(props);
      expect(event.dimension.value).toBe('adminTrust');
    });

    it('should allow driving with race source', () => {
      const props = {
        ...validProps,
        dimension: TeamRatingDimensionKey.create('driving'),
        source: { type: 'race' as const, id: 'race-456' },
      };
      const event = TeamRatingEvent.create(props);
      expect(event.dimension.value).toBe('driving');
    });
  });

  describe('rehydrate', () => {
    it('should rehydrate event from stored data', () => {
      const event = TeamRatingEvent.rehydrate(validProps);

      expect(event.id.value).toBe(validProps.id.value);
      expect(event.teamId).toBe(validProps.teamId);
      expect(event.dimension.value).toBe('driving');
      expect(event.delta.value).toBe(10);
    });

    it('should rehydrate event with optional weight', () => {
      const props = { ...validProps, weight: 2 };
      const event = TeamRatingEvent.rehydrate(props);

      expect(event.weight).toBe(2);
    });

    it('should return true for same ID', () => {
      const event1 = TeamRatingEvent.create(validProps);
      const event2 = TeamRatingEvent.rehydrate(validProps);

      expect(event1.equals(event2)).toBe(true);
    });

    it('should return false for different IDs', () => {
      const event1 = TeamRatingEvent.create(validProps);
      const event2 = TeamRatingEvent.create({
        ...validProps,
        id: TeamRatingEventId.create('123e4567-e89b-12d3-a456-426614174001'),
      });

      expect(event1.equals(event2)).toBe(false);
    });
  });

  describe('toJSON', () => {
    it('should return plain object representation', () => {
      const event = TeamRatingEvent.create(validProps);
      const json = event.toJSON();

      expect(json).toEqual({
        id: validProps.id.value,
        teamId: validProps.teamId,
        dimension: 'driving',
        delta: 10,
        weight: undefined,
        occurredAt: validProps.occurredAt.toISOString(),
        createdAt: validProps.createdAt.toISOString(),
        source: validProps.source,
        reason: validProps.reason,
        visibility: validProps.visibility,
        version: 1,
      });
    });

    it('should include weight when present', () => {
      const props = { ...validProps, weight: 2 };
      const event = TeamRatingEvent.create(props);
      const json = event.toJSON();

      expect(json).toHaveProperty('weight', 2);
    });
  });
});