import type { EventBus } from '@nestjs/cqrs';
import type { IInquiryRepository } from '../../domain/repositories/inquiry.repository';
import { CreateInquiryCommand } from '../commands/create-inquiry/create-inquiry.command';
import { CreateInquiryHandler } from '../commands/create-inquiry/create-inquiry.handler';
import { SanitizeHtmlService } from '../services/sanitize-html.service';

describe('CreateInquiryHandler', () => {
  let handler: CreateInquiryHandler;
  let mockInquiryRepo: { [K in keyof IInquiryRepository]: ReturnType<typeof vi.fn> };
  let mockEventBus: { publish: ReturnType<typeof vi.fn> };
  let mockPrisma: {
    listing: { findUnique: ReturnType<typeof vi.fn> };
  };
  let sanitizer: SanitizeHtmlService;

  beforeEach(() => {
    mockInquiryRepo = {
      findById: vi.fn(),
      save: vi.fn(),
      markAsRead: vi.fn(),
      findByListing: vi.fn(),
      findByAgent: vi.fn(),
      countUnreadByAgent: vi.fn(),
    };

    mockEventBus = { publish: vi.fn() };

    mockPrisma = {
      listing: { findUnique: vi.fn() },
    };

    const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };

    sanitizer = new SanitizeHtmlService();

    handler = new CreateInquiryHandler(
      mockInquiryRepo as any,
      mockEventBus as unknown as EventBus,
      mockPrisma as any,
      mockLogger as any,
      sanitizer,
    );
  });

  it('creates an inquiry successfully', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue({ id: 'listing-1' });
    mockInquiryRepo.save.mockResolvedValue(undefined);

    const command = new CreateInquiryCommand(
      'user-1',
      'listing-1',
      'Tôi muốn xem nhà',
      '0901234567',
    );

    const result = await handler.execute(command);

    expect(result.id).toBeDefined();
    expect(result.listingId).toBe('listing-1');
    expect(result.createdAt).toBeDefined();
    expect(mockInquiryRepo.save).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalled();
  });

  it('throws NotFoundException when listing not found', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue(null);

    const command = new CreateInquiryCommand(
      'user-1',
      'listing-not-exist',
      'Tôi muốn xem nhà',
      null,
    );

    await expect(handler.execute(command)).rejects.toThrow(
      "Listing with id 'listing-not-exist' not found",
    );
    expect(mockInquiryRepo.save).not.toHaveBeenCalled();
  });

  it('publishes domain events after saving', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue({ id: 'listing-1' });
    mockInquiryRepo.save.mockResolvedValue(undefined);

    const command = new CreateInquiryCommand(
      'user-1',
      'listing-1',
      'Cho tôi hỏi giá',
      null,
    );

    await handler.execute(command);

    expect(mockEventBus.publish).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalledWith(
      expect.objectContaining({
        eventName: 'inquiry.received',
        listingId: 'listing-1',
        userId: 'user-1',
      }),
    );
  });

  it('sanitizes <script> and on* handlers before persisting', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue({ id: 'listing-1' });
    mockInquiryRepo.save.mockResolvedValue(undefined);

    const command = new CreateInquiryCommand(
      'user-1',
      'listing-1',
      '<p>Chào anh</p><script>alert(1)</script><img src=x onerror="alert(2)"/>',
      null,
    );

    await handler.execute(command);

    const savedInquiry = mockInquiryRepo.save.mock.calls[0][0];
    const persisted: string = savedInquiry.message;

    expect(persisted).toContain('<p>Chào anh</p>');
    expect(persisted).not.toContain('<script');
    expect(persisted).not.toContain('alert(1)');
    expect(persisted).not.toContain('onerror');
    expect(persisted).not.toContain('<img');
  });

  it('forces rel/target on anchor tags and drops javascript: URLs', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue({ id: 'listing-1' });
    mockInquiryRepo.save.mockResolvedValue(undefined);

    const command = new CreateInquiryCommand(
      'user-1',
      'listing-1',
      '<a href="https://example.com">ok</a><a href="javascript:alert(1)">bad</a>',
      null,
    );

    await handler.execute(command);

    const persisted: string = mockInquiryRepo.save.mock.calls[0][0].message;

    expect(persisted).toContain('href="https://example.com"');
    expect(persisted).toContain('rel="noopener noreferrer nofollow"');
    expect(persisted).toContain('target="_blank"');
    expect(persisted).not.toContain('javascript:');
  });
});
