import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { PrintButtonComponent } from './print-button.component';
import { PrintService } from '../../services';
import { PrinterType, Printer } from '../../models';
import { InfoButtonComponent } from '@isa/ui/buttons';
import { NgIcon } from '@ng-icons/core';

// Mock Scandit modules to avoid loading external dependencies
jest.mock('scandit-web-datacapture-core', () => ({}));
jest.mock('scandit-web-datacapture-barcode', () => ({}));

describe('PrintButtonComponent', () => {
  let spectator: Spectator<PrintButtonComponent>;
  let printServiceMock: jest.Mocked<PrintService>;

  // Mock printer for testing
  const mockPrinter: Printer = {
    key: 'printer-1',
    value: 'Test Printer',
    selected: true,
    enabled: true,
  };

  // Mock print function that returns a Promise
  const mockPrintFn = jest.fn().mockResolvedValue(undefined);
  const createComponent = createComponentFactory({
    component: PrintButtonComponent,
    imports: [InfoButtonComponent, NgIcon],
    providers: [
      {
        provide: NgIcon,
        useValue: {
          name: 'isaActionPrinter',
        },
      },
    ],
    mocks: [PrintService],
    detectChanges: false,
  });
  // We no longer need a separate host factory here

  beforeEach(() => {
    // Create the component with required inputs
    spectator = createComponent({
      props: {
        printerType: PrinterType.LABEL,
        printFn: mockPrintFn,
        directPrint: undefined,
      },
    });

    // Get the mocked print service
    printServiceMock = spectator.inject(
      PrintService,
    ) as jest.Mocked<PrintService>;

    // Setup mock implementation for print service
    printServiceMock.print.mockResolvedValue({ printer: mockPrinter });

    // Reset the print function mock before each test
    mockPrintFn.mockClear();

    // Trigger change detection
    spectator.detectChanges();
  });

  it('should create the component', () => {
    expect(spectator.component).toBeTruthy();
  });

  it('should render a button with the correct data attributes', () => {
    const button = spectator.query('button');
    expect(button).toExist();
    expect(button).toHaveAttribute('data-what', 'button');
    expect(button).toHaveAttribute('data-which', 'print');
  });

  it('should render the printer icon', () => {
    const icon = spectator.query('ng-icon');
    expect(icon).toExist();
    expect(icon).toHaveAttribute('name', 'isaActionPrinter');
  });

  it('should call print service with correct parameters when button is clicked', async () => {
    // Act
    await spectator.click('button');

    // Assert
    expect(printServiceMock.print).toHaveBeenCalledTimes(1);
    expect(printServiceMock.print).toHaveBeenCalledWith({
      printerType: PrinterType.LABEL,
      printFn: mockPrintFn,
      directPrint: undefined,
    });
  });

  it('should set printing signal to true during print operation and false afterward', async () => {
    // Setup spy to monitor signal changes
    jest.spyOn(spectator.component.printing, 'set');

    // Act
    await spectator.click('button');

    // Assert
    expect(spectator.component.printing.set).toHaveBeenNthCalledWith(1, true);
    expect(spectator.component.printing.set).toHaveBeenNthCalledWith(2, false);
  });

  it('should call the provided printFn via the print service', async () => {
    // Setup the mock print service to actually call the provided function
    printServiceMock.print.mockImplementationOnce(async ({ printFn }) => {
      await printFn(mockPrinter);
      return { printer: mockPrinter };
    });

    // Act
    await spectator.click('button');

    // Assert
    expect(mockPrintFn).toHaveBeenCalledTimes(1);
    expect(mockPrintFn).toHaveBeenCalledWith(mockPrinter);
  });

  it('should pass directPrint parameter correctly to print service', async () => {
    // Arrange
    spectator.setInput('directPrint', true);
    spectator.detectChanges();

    // Act
    await spectator.click('button');

    // Assert
    expect(printServiceMock.print).toHaveBeenCalledWith(
      expect.objectContaining({ directPrint: true }),
    );

    // Test with false value
    spectator.setInput('directPrint', false);
    spectator.detectChanges();
    printServiceMock.print.mockClear();

    await spectator.click('button');
    expect(printServiceMock.print).toHaveBeenCalledWith(
      expect.objectContaining({ directPrint: false }),
    );
  });
  it('should handle different printer types correctly', async () => {
    // Arrange
    spectator.setInput('printerType', PrinterType.OFFICE);
    spectator.detectChanges();

    // Act
    await spectator.click('button');

    // Assert
    expect(printServiceMock.print).toHaveBeenCalledWith(
      expect.objectContaining({ printerType: PrinterType.OFFICE }),
    );
  });
  it('should handle print errors and reset printing state', async () => {
    // Arrange
    const testError = new Error('Print failed');
    printServiceMock.print.mockRejectedValueOnce(testError);

    // Act
    await spectator.click('button');

    // Assert
    // Verify that the print service was called
    expect(printServiceMock.print).toHaveBeenCalled();
    // Verify that the printing state was reset to false after the error
    expect(spectator.component.printing()).toBe(false);
  });
  it('should have a content projection area', () => {
    // We cannot test ng-content directly in a regular component test
    // Instead, let's verify the structure that would contain the content
    const buttonSpan = spectator.query('button span');
    expect(buttonSpan).toExist();
    expect(buttonSpan).toHaveAttribute('uiInfoButtonLabel');
  });
});
