import { ListingEntity } from '@modules/listings/domain/entities/listing.entity';
import { Price } from '@modules/listings/domain/value-objects/price.vo';
import { CheckQuotaQuery, MeterUsageCommand } from '@modules/subscriptions';
import { PromoteFeaturedListingCommand } from '../commands/promote-featured-listing/promote-featured-listing.command';
import {
  FEATURED_LISTINGS_PROMOTED_METRIC,
  PromoteFeaturedListingHandler,
} from '../commands/promote-featured-listing/promote-featured-listing.handler';

function createListing(
  id = 'listing-1',
  sellerId = 'seller-1',
  agentId: string | null = null,
  status: 'DRAFT' | 'PENDING_REVIEW' | 'ACTIVE' = 'ACTIVE',
): ListingEntity {
  const price = Price.create(2_000_000_000n).unwrap();
  const listing = ListingEntity.createNew(id, 'prop-1', sellerId, 'SALE', price, 80, agentId ?? undefined);
  if (status === 'PENDING_REVIEW' || status === 'ACTIVE') listing.submitForReview();
  if (status === 'ACTIVE') listing.approve();
  listing.clearDomainEvents();
  return listing;
}

describe('PromoteFeaturedListingHandler', () => {
  let handler: PromoteFeaturedListingHandler;
  let mockListingRepo: { findById: ReturnType<typeof vi.fn> };
  let mockPrisma: { listing: { update: ReturnType<typeof vi.fn> } };
  let mockCommandBus: { execute: ReturnType<typeof vi.fn> };
  let mockQueryBus: { execute: ReturnType<typeof vi.fn> };
  let mockLogger: { log: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockListingRepo = { findById: vi.fn() };
    mockPrisma = { listing: { update: vi.fn().mockResolvedValue(undefined) } };
    mockCommandBus = { execute: vi.fn().mockResolvedValue({ usageRecordId: 'u-1' }) };
    mockQueryBus = {
      execute: vi.fn().mockResolvedValue({
        metric: FEATURED_LISTINGS_PROMOTED_METRIC,
        limit: 5,
        used: 0,
        remaining: 5,
        allowed: true,
      }),
    };
    mockLogger = { log: vi.fn(), error: vi.fn() };

    handler = new PromoteFeaturedListingHandler(
      mockListingRepo as any,
      mockPrisma as any,
      mockCommandBus as any,
      mockQueryBus as any,
      mockLogger as any,
    );
  });

  it('promotes an active listing when owner has quota', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'seller-1', null, 'ACTIVE'));

    const before = Date.now();
    const result = await handler.execute(
      new PromoteFeaturedListingCommand('listing-1', 'seller-1', 7),
    );
    const after = Date.now();

    expect(result.listingId).toBe('listing-1');
    expect(result.durationDays).toBe(7);
    expect(result.quotaRemaining).toBe(4);

    const parsed = Date.parse(result.featuredUntil);
    expect(parsed).toBeGreaterThanOrEqual(before + 7 * 24 * 60 * 60 * 1000);
    expect(parsed).toBeLessThanOrEqual(after + 7 * 24 * 60 * 60 * 1000 + 1000);

    expect(mockPrisma.listing.update).toHaveBeenCalledTimes(1);
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
    const meterCall = mockCommandBus.execute.mock.calls[0][0];
    expect(meterCall).toBeInstanceOf(MeterUsageCommand);
    expect(meterCall.metric).toBe(FEATURED_LISTINGS_PROMOTED_METRIC);
    expect(meterCall.count).toBe(1);
  });

  it('allows the assigned agent to promote', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'seller-1', 'agent-1', 'ACTIVE'));

    const result = await handler.execute(
      new PromoteFeaturedListingCommand('listing-1', 'agent-1', 3),
    );
    expect(result.durationDays).toBe(3);
    expect(mockPrisma.listing.update).toHaveBeenCalled();
  });

  it('extends featuredUntil from the existing expiry when still active', async () => {
    const listing = createListing('listing-1', 'seller-1', null, 'ACTIVE');
    const future = new Date(Date.now() + 2 * 24 * 60 * 60 * 1000);
    (listing as unknown as { _featuredUntil: Date })._featuredUntil = future;
    mockListingRepo.findById.mockResolvedValue(listing);

    const result = await handler.execute(
      new PromoteFeaturedListingCommand('listing-1', 'seller-1', 7),
    );

    const expected = future.getTime() + 7 * 24 * 60 * 60 * 1000;
    expect(Math.abs(Date.parse(result.featuredUntil) - expected)).toBeLessThan(1000);
  });

  it('rejects promote when quota exhausted', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'seller-1', null, 'ACTIVE'));
    mockQueryBus.execute.mockResolvedValue({
      metric: FEATURED_LISTINGS_PROMOTED_METRIC,
      limit: 5,
      used: 5,
      remaining: 0,
      allowed: false,
    });

    await expect(
      handler.execute(new PromoteFeaturedListingCommand('listing-1', 'seller-1', 7)),
    ).rejects.toThrow(/Đã dùng hết|nâng cấp/);

    expect(mockPrisma.listing.update).not.toHaveBeenCalled();
    expect(mockCommandBus.execute).not.toHaveBeenCalled();
  });

  it('rejects non-owner / non-agent', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'seller-1', null, 'ACTIVE'));

    await expect(
      handler.execute(new PromoteFeaturedListingCommand('listing-1', 'stranger', 7)),
    ).rejects.toThrow(/người bán|môi giới/);
  });

  it('rejects non-ACTIVE listing', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'seller-1', null, 'DRAFT'));

    await expect(
      handler.execute(new PromoteFeaturedListingCommand('listing-1', 'seller-1', 7)),
    ).rejects.toThrow(/hoạt động/);
  });

  it('rejects invalid durationDays', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'seller-1', null, 'ACTIVE'));

    await expect(
      handler.execute(new PromoteFeaturedListingCommand('listing-1', 'seller-1', 5 as unknown as 3)),
    ).rejects.toThrow(/Thời lượng/);
  });

  it('passes CheckQuotaQuery with the featured_listings_promoted metric', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'seller-1', null, 'ACTIVE'));

    await handler.execute(new PromoteFeaturedListingCommand('listing-1', 'seller-1', 7));

    const queryArg = mockQueryBus.execute.mock.calls[0][0];
    expect(queryArg).toBeInstanceOf(CheckQuotaQuery);
    expect(queryArg.metric).toBe(FEATURED_LISTINGS_PROMOTED_METRIC);
    expect(queryArg.userId).toBe('seller-1');
  });
});
