import { describe, it, expect } from 'vitest';
import { AggregateRoot } from '../aggregate-root';
import { type DomainEvent } from '../domain-event';

class TestAggregate extends AggregateRoot {
  doSomething(): void {
    this.addDomainEvent({
      eventName: 'TestHappened',
      occurredAt: new Date(),
      aggregateId: this.id,
    });
  }
}

describe('AggregateRoot', () => {
  it('should collect domain events', () => {
    const agg = new TestAggregate('agg-1');
    agg.doSomething();
    agg.doSomething();
    expect(agg.domainEvents).toHaveLength(2);
    expect(agg.domainEvents[0]!.eventName).toBe('TestHappened');
  });

  it('should clear domain events and return them', () => {
    const agg = new TestAggregate('agg-1');
    agg.doSomething();
    const events = agg.clearDomainEvents();
    expect(events).toHaveLength(1);
    expect(agg.domainEvents).toHaveLength(0);
  });

  it('should return a copy of domain events (not mutable reference)', () => {
    const agg = new TestAggregate('agg-1');
    agg.doSomething();
    const events = agg.domainEvents;
    (events as DomainEvent[]).push({
      eventName: 'Fake',
      occurredAt: new Date(),
      aggregateId: 'x',
    });
    expect(agg.domainEvents).toHaveLength(1);
  });

  it('should return uncommitted events without clearing via getUncommittedEvents()', () => {
    const agg = new TestAggregate('agg-1');
    agg.doSomething();
    agg.doSomething();

    const events = agg.getUncommittedEvents();
    expect(events).toHaveLength(2);
    // Should not clear events
    expect(agg.domainEvents).toHaveLength(2);
  });

  it('should return defensive copy from getUncommittedEvents()', () => {
    const agg = new TestAggregate('agg-1');
    agg.doSomething();

    const events = agg.getUncommittedEvents();
    events.push({ eventName: 'Fake', occurredAt: new Date(), aggregateId: 'x' });
    expect(agg.getUncommittedEvents()).toHaveLength(1);
  });

  it('should clear and return events via commit()', () => {
    const agg = new TestAggregate('agg-1');
    agg.doSomething();
    agg.doSomething();
    agg.doSomething();

    const events = agg.commit();
    expect(events).toHaveLength(3);
    expect(agg.domainEvents).toHaveLength(0);
    expect(agg.getUncommittedEvents()).toHaveLength(0);
  });

  it('commit() should behave identically to clearDomainEvents()', () => {
    const agg1 = new TestAggregate('agg-1');
    const agg2 = new TestAggregate('agg-2');
    agg1.doSomething();
    agg2.doSomething();

    const cleared = agg1.clearDomainEvents();
    const committed = agg2.commit();

    expect(cleared).toHaveLength(committed.length);
    expect(agg1.domainEvents).toHaveLength(0);
    expect(agg2.domainEvents).toHaveLength(0);
  });

  it('should return empty arrays when no events exist', () => {
    const agg = new TestAggregate('agg-1');
    expect(agg.getUncommittedEvents()).toHaveLength(0);
    expect(agg.commit()).toHaveLength(0);
  });
});
