import { describe, it, expect } from 'vitest';
import { ListingEntity } from '../entities/listing.entity';
import { Price } from '../value-objects/price.vo';

describe('ListingEntity', () => {
  const makeDefaultListing = () => {
    const price = Price.create(5_000_000_000n).unwrap();
    return ListingEntity.createNew(
      'listing-1',
      'property-1',
      'seller-1',
      'SALE',
      price,
      100,
      'agent-1',
    );
  };

  it('should create a new listing in DRAFT status', () => {
    const listing = makeDefaultListing();
    expect(listing.status).toBe('DRAFT');
    expect(listing.propertyId).toBe('property-1');
    expect(listing.sellerId).toBe('seller-1');
    expect(listing.transactionType).toBe('SALE');
    expect(listing.pricePerM2).toBe(50_000_000);
    expect(listing.viewCount).toBe(0);
  });

  it('should emit ListingCreatedEvent on creation', () => {
    const listing = makeDefaultListing();
    const events = listing.domainEvents;
    expect(events).toHaveLength(1);
    expect(events[0]!.eventName).toBe('listing.created');
  });

  it('should transition DRAFT -> PENDING_REVIEW and emit ListingStatusChangedEvent', () => {
    const listing = makeDefaultListing();
    listing.clearDomainEvents();
    listing.submitForReview();
    expect(listing.status).toBe('PENDING_REVIEW');

    const events = listing.domainEvents;
    expect(events).toHaveLength(1);
    expect(events[0]!.eventName).toBe('listing.status_changed');
  });

  it('should transition PENDING_REVIEW -> ACTIVE and emit ListingApprovedEvent', () => {
    const listing = makeDefaultListing();
    listing.clearDomainEvents();
    listing.submitForReview();
    listing.approve();
    expect(listing.status).toBe('ACTIVE');
    expect(listing.publishedAt).toBeTruthy();

    const events = listing.domainEvents;
    expect(events.some((e) => e.eventName === 'listing.approved')).toBe(true);
  });

  it('should reject a PENDING_REVIEW listing and emit ListingStatusChangedEvent', () => {
    const listing = makeDefaultListing();
    listing.submitForReview();
    listing.clearDomainEvents();
    listing.reject('Ảnh không rõ ràng');
    expect(listing.status).toBe('REJECTED');
    expect(listing.moderationNotes).toBe('Ảnh không rõ ràng');

    const events = listing.domainEvents;
    expect(events.some((e) => e.eventName === 'listing.status_changed')).toBe(true);
  });

  it('should transition ACTIVE -> SOLD and emit ListingSoldEvent', () => {
    const listing = makeDefaultListing();
    listing.clearDomainEvents();
    listing.submitForReview();
    listing.approve();
    listing.clearDomainEvents();
    listing.transitionTo('SOLD');
    expect(listing.status).toBe('SOLD');

    const events = listing.domainEvents;
    expect(events.some((e) => e.eventName === 'listing.sold')).toBe(true);
  });

  it('should throw on invalid status transition', () => {
    const listing = makeDefaultListing();
    expect(() => listing.transitionTo('ACTIVE')).toThrow('Không thể chuyển trạng thái');
  });

  it('should not allow transition from SOLD', () => {
    const listing = makeDefaultListing();
    listing.submitForReview();
    listing.approve();
    listing.transitionTo('SOLD');
    expect(() => listing.transitionTo('ACTIVE')).toThrow();
  });

  it('should allow REJECTED -> DRAFT', () => {
    const listing = makeDefaultListing();
    listing.submitForReview();
    listing.reject('Nội dung vi phạm');
    listing.transitionTo('DRAFT');
    expect(listing.status).toBe('DRAFT');
  });

  it('should increment view count', () => {
    const listing = makeDefaultListing();
    listing.incrementViewCount();
    listing.incrementViewCount();
    expect(listing.viewCount).toBe(2);
  });

  describe('updateContent', () => {
    it('should update priceVND and recalculate pricePerM2', () => {
      const listing = makeDefaultListing();
      listing.clearDomainEvents();

      const fields = listing.updateContent({ priceVND: 6_000_000_000n, areaM2: 100 });

      expect(fields).toContain('priceVND');
      expect(listing.price.amountVND).toBe(6_000_000_000n);
      expect(listing.pricePerM2).toBe(60_000_000);
    });

    it('should update rentPriceMonthly', () => {
      const listing = makeDefaultListing();
      listing.clearDomainEvents();

      const fields = listing.updateContent({ rentPriceMonthly: 30_000_000n });

      expect(fields).toContain('rentPriceMonthly');
      expect(listing.rentPriceMonthly).toBe(30_000_000n);
    });

    it('should throw ValidationException for invalid price', () => {
      const listing = makeDefaultListing();

      expect(() => listing.updateContent({ priceVND: -1n })).toThrow();
    });

    it('should return empty array when no fields changed', () => {
      const listing = makeDefaultListing();
      const fields = listing.updateContent({});
      expect(fields).toEqual([]);
    });

    it('should emit ListingPriceChangedEvent when price actually changes', () => {
      const listing = makeDefaultListing();
      listing.clearDomainEvents();

      listing.updateContent({ priceVND: 6_000_000_000n, areaM2: 100 });

      const events = listing.domainEvents;
      const priceEvent = events.find((e) => e.eventName === 'listing.price_changed');
      expect(priceEvent).toBeDefined();
      expect((priceEvent as { oldPrice: bigint; newPrice: bigint }).oldPrice).toBe(
        5_000_000_000n,
      );
      expect((priceEvent as { oldPrice: bigint; newPrice: bigint }).newPrice).toBe(
        6_000_000_000n,
      );
    });

    it('should NOT emit ListingPriceChangedEvent when price stays the same', () => {
      const listing = makeDefaultListing();
      listing.clearDomainEvents();

      listing.updateContent({ priceVND: 5_000_000_000n, areaM2: 100 });

      const events = listing.domainEvents;
      expect(events.some((e) => e.eventName === 'listing.price_changed')).toBe(false);
    });
  });

  describe('markEditedForReModeration', () => {
    it('should transition ACTIVE -> PENDING_REVIEW and emit events', () => {
      const listing = makeDefaultListing();
      listing.submitForReview();
      listing.approve();
      listing.clearDomainEvents();

      listing.markEditedForReModeration('property-1', ['title']);

      expect(listing.status).toBe('PENDING_REVIEW');
      const events = listing.domainEvents;
      expect(events.some((e) => e.eventName === 'listing.status_changed')).toBe(true);
      expect(events.some((e) => e.eventName === 'listing.updated')).toBe(true);
    });

    it('should NOT change DRAFT status but still emit ListingUpdatedEvent', () => {
      const listing = makeDefaultListing();
      listing.clearDomainEvents();

      listing.markEditedForReModeration('property-1', ['title']);

      expect(listing.status).toBe('DRAFT');
      const events = listing.domainEvents;
      expect(events).toHaveLength(1);
      expect(events[0]!.eventName).toBe('listing.updated');
    });
  });
});
