import { ListingEntity } from '@modules/listings/domain/entities/listing.entity';
import { Price } from '@modules/listings/domain/value-objects/price.vo';
import { AdminFeatureListingCommand } from '../commands/admin-feature-listing/admin-feature-listing.command';
import { AdminFeatureListingHandler } from '../commands/admin-feature-listing/admin-feature-listing.handler';

function createListing(
  id = 'listing-1',
  status: 'DRAFT' | 'PENDING_REVIEW' | 'ACTIVE' = 'ACTIVE',
): ListingEntity {
  const price = Price.create(1_500_000_000n).unwrap();
  const listing = ListingEntity.createNew(id, 'prop-1', 'seller-1', 'SALE', price, 60);
  if (status === 'PENDING_REVIEW' || status === 'ACTIVE') listing.submitForReview();
  if (status === 'ACTIVE') listing.approve();
  listing.clearDomainEvents();
  return listing;
}

describe('AdminFeatureListingHandler', () => {
  let handler: AdminFeatureListingHandler;
  let mockListingRepo: { findById: ReturnType<typeof vi.fn> };
  let mockPrisma: {
    $transaction: ReturnType<typeof vi.fn>;
    listing: { update: ReturnType<typeof vi.fn> };
    adminAuditLog: { create: ReturnType<typeof vi.fn> };
  };
  let mockLogger: { log: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };
  let transactionOps: unknown[];

  beforeEach(() => {
    transactionOps = [];
    mockListingRepo = { findById: vi.fn() };
    const listingUpdate = vi.fn().mockImplementation((args: unknown) => {
      transactionOps.push({ kind: 'listing.update', args });
      return { kind: 'listing.update', args };
    });
    const auditLogCreate = vi.fn().mockImplementation((args: unknown) => {
      transactionOps.push({ kind: 'audit.create', args });
      return { kind: 'audit.create', args };
    });
    const $transaction = vi.fn().mockImplementation(async (ops: unknown[]) => ops);
    mockPrisma = {
      $transaction,
      listing: { update: listingUpdate },
      adminAuditLog: { create: auditLogCreate },
    };
    mockLogger = { log: vi.fn(), error: vi.fn() };

    handler = new AdminFeatureListingHandler(mockListingRepo as any, mockPrisma as any, mockLogger as any);
  });

  it('features a listing with durationDays and writes audit log', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'ACTIVE'));

    const before = Date.now();
    const result = await handler.execute(
      new AdminFeatureListingCommand('listing-1', 'admin-1', 'feature', 14, 'Đền bù lỗi hiển thị', '10.0.0.1'),
    );
    const after = Date.now();

    expect(result.action).toBe('feature');
    expect(result.listingId).toBe('listing-1');
    expect(result.featuredUntil).not.toBeNull();
    const parsed = Date.parse(result.featuredUntil!);
    expect(parsed).toBeGreaterThanOrEqual(before + 14 * 24 * 60 * 60 * 1000);
    expect(parsed).toBeLessThanOrEqual(after + 14 * 24 * 60 * 60 * 1000 + 1000);

    expect(mockPrisma.$transaction).toHaveBeenCalledTimes(1);
    const auditOp = transactionOps.find((op: any) => op.kind === 'audit.create') as any;
    expect(auditOp.args.data.action).toBe('LISTING_FEATURED');
    expect(auditOp.args.data.actorId).toBe('admin-1');
    expect(auditOp.args.data.targetId).toBe('listing-1');
    expect(auditOp.args.data.targetType).toBe('LISTING');
    expect(auditOp.args.data.metadata.reason).toBe('Đền bù lỗi hiển thị');
    expect(auditOp.args.data.metadata.durationDays).toBe(14);
    expect(auditOp.args.data.ipAddress).toBe('10.0.0.1');
  });

  it('unfeatures a listing and logs LISTING_UNFEATURED', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'ACTIVE'));

    const result = await handler.execute(
      new AdminFeatureListingCommand('listing-1', 'admin-1', 'unfeature', null, 'Vi phạm chính sách nội dung', null),
    );

    expect(result.action).toBe('unfeature');
    expect(result.featuredUntil).toBeNull();

    const updateOp = transactionOps.find((op: any) => op.kind === 'listing.update') as any;
    expect(updateOp.args.data.featuredUntil).toBeNull();

    const auditOp = transactionOps.find((op: any) => op.kind === 'audit.create') as any;
    expect(auditOp.args.data.action).toBe('LISTING_UNFEATURED');
    expect(auditOp.args.data.metadata.featuredUntil).toBeNull();
  });

  it('rejects short reason', async () => {
    mockListingRepo.findById.mockResolvedValue(createListing('listing-1', 'ACTIVE'));

    await expect(
      handler.execute(new AdminFeatureListingCommand('listing-1', 'admin-1', 'feature', 7, 'bad', null)),
    ).rejects.toThrow(/Lý do/);

    expect(mockPrisma.$transaction).not.toHaveBeenCalled();
  });

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

    await expect(
      handler.execute(new AdminFeatureListingCommand('listing-1', 'admin-1', 'feature', 5, 'reason long enough', null)),
    ).rejects.toThrow(/Thời lượng/);
  });

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

    await expect(
      handler.execute(
        new AdminFeatureListingCommand('listing-1', 'admin-1', 'feature', null, 'reason long enough', null),
      ),
    ).rejects.toThrow(/Thời lượng/);
  });

  it('throws NotFoundException for non-existent listing', async () => {
    mockListingRepo.findById.mockResolvedValue(null);

    await expect(
      handler.execute(new AdminFeatureListingCommand('missing', 'admin-1', 'feature', 7, 'reason long enough', null)),
    ).rejects.toThrow('Listing');
  });
});
