import { SpectatorService, createServiceFactory } from '@ngneat/spectator/jest';
import { PrintService } from './print.service';
import { Platform } from '@angular/cdk/platform';
import {
  ListResponseArgsOfKeyValueDTOOfStringAndString,
  PrintService as PrintApiService,
} from '@generated/swagger/print-api';
import { of } from 'rxjs';
import { PrinterType } from '../models';
import { PrinterDialogResult } from '../print-dialog/print-dialog.component';

// Mock dialog function that can be configured in tests
const mockDialogClosedObservable = {
  closed: of<PrinterDialogResult | undefined>({ printer: undefined }),
};
const mockDialogFn = jest.fn().mockReturnValue(mockDialogClosedObservable);

jest.mock('@isa/ui/dialog', () => ({
  injectDialog: jest.fn().mockImplementation(() => mockDialogFn),
}));

jest.mock('../print-dialog/print-dialog.component', () => ({
  PrintDialogComponent: jest
    .fn()
    .mockReturnValue(class MockPrintDialogComponent {}),
}));

describe('PrintService', () => {
  let spectator: SpectatorService<PrintService>;
  const createService = createServiceFactory({
    service: PrintService,
    mocks: [PrintApiService, Platform],
  });

  beforeEach(() => {
    spectator = createService();
  });

  it('should be created', () => {
    expect(spectator.service).toBeTruthy();
  });

  describe('labelPrinters', () => {
    it('should call PrintLabelPrinters and return printers', async () => {
      const mockPrinters: ListResponseArgsOfKeyValueDTOOfStringAndString = {
        error: false,
        result: [
          {
            key: 'printer1',
            value: 'Printer 1',
          },
          {
            key: 'printer2',
            value: 'Printer 2',
          },
        ],
      };

      const printApiService: jest.Mocked<PrintApiService> =
        spectator.inject(PrintApiService);

      printApiService.PrintLabelPrinters.mockReturnValue(of(mockPrinters));

      const printers = await spectator.service.labelPrinters();

      expect(printApiService.PrintLabelPrinters).toHaveBeenCalled();
      expect(printers).toEqual(mockPrinters.result);
    });
  });

  describe('officePrinters', () => {
    it('should call PrintOfficePrinters and return printers', async () => {
      const mockPrinters: ListResponseArgsOfKeyValueDTOOfStringAndString = {
        error: false,
        result: [
          {
            key: 'printer1',
            value: 'Printer 1',
          },
          {
            key: 'printer2',
            value: 'Printer 2',
          },
        ],
      };

      const printApiService: jest.Mocked<PrintApiService> =
        spectator.inject(PrintApiService);

      printApiService.PrintOfficePrinters.mockReturnValue(of(mockPrinters));

      const printers = await spectator.service.officePrinters();

      expect(printApiService.PrintOfficePrinters).toHaveBeenCalled();
      expect(printers).toEqual(mockPrinters.result);
    });
  });

  describe('printers', () => {
    it('should call labelPrinters when printerType is LABEL', async () => {
      const labelPrintersSpy = jest
        .spyOn(spectator.service, 'labelPrinters')
        .mockReturnValue(Promise.resolve([]));
      await spectator.service.printers(PrinterType.LABEL);
      expect(labelPrintersSpy).toHaveBeenCalled();
    });

    it('should call officePrinters when printerType is OFFICE', async () => {
      const officePrintersSpy = jest
        .spyOn(spectator.service, 'officePrinters')
        .mockReturnValue(Promise.resolve([]));
      await spectator.service.printers(PrinterType.OFFICE);
      expect(officePrintersSpy).toHaveBeenCalled();
    });
  });
  describe('print', () => {
    const mockPrinters = [
      { key: 'printer1', value: 'Printer 1', selected: false, enabled: true },
      { key: 'printer2', value: 'Printer 2', selected: true, enabled: true },
    ];

    let mockPrint: jest.Mock;
    let platform: jest.Mocked<Platform>;
    let printSpy: jest.SpyInstance;

    beforeEach(() => {
      mockPrint = jest.fn().mockResolvedValue(undefined);
      platform = spectator.inject(Platform);

      // Mock the printers method to return our mock printers
      printSpy = jest
        .spyOn(spectator.service, 'printers')
        .mockResolvedValue(mockPrinters);

      // Reset platform flags before each test
      platform.ANDROID = false;
      platform.IOS = false;

      // Reset the dialog mock before each test
      mockDialogFn.mockClear();
      mockDialogClosedObservable.closed = of({ printer: mockPrinters[1] });
    });

    it('should fetch printers of the specified type', async () => {
      // Act
      await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(printSpy).toHaveBeenCalledWith(PrinterType.LABEL);
    });

    it('should attempt direct printing on desktop when a printer is selected', async () => {
      // Act
      await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(mockPrint).toHaveBeenCalledWith(mockPrinters[1]);
      expect(mockDialogFn).not.toHaveBeenCalled();
    });

    it('should return the selected printer after successful direct print', async () => {
      // Act
      const result = await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(result).toEqual({ printer: mockPrinters[1] });
    });

    it('should show dialog with error when direct printing fails', async () => {
      // Arrange
      const printError = new Error('Print failed');
      mockPrint.mockRejectedValueOnce(printError);

      // Act
      await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(mockDialogFn).toHaveBeenCalledWith({
        data: {
          printers: mockPrinters,
          error: printError,
          print: mockPrint,
        },
      });
    });

    it('should not attempt direct printing on Android', async () => {
      // Arrange
      platform.ANDROID = true;

      // Act
      await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(mockPrint).not.toHaveBeenCalled();
      expect(mockDialogFn).toHaveBeenCalled();
    });

    it('should not attempt direct printing on iOS', async () => {
      // Arrange
      platform.IOS = true;

      // Act
      await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(mockPrint).not.toHaveBeenCalled();
      expect(mockDialogFn).toHaveBeenCalled();
    });

    it('should show print dialog when no printer is selected', async () => {
      // Arrange
      const printersWithoutSelection = [
        { key: 'printer1', value: 'Printer 1', selected: false, enabled: true },
        { key: 'printer2', value: 'Printer 2', selected: false, enabled: true },
      ];

      printSpy.mockResolvedValueOnce(printersWithoutSelection);

      // Act
      await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(mockPrint).not.toHaveBeenCalled();
      expect(mockDialogFn).toHaveBeenCalled();
    });

    it('should pass the correct data to the print dialog', async () => {
      // Arrange
      platform.ANDROID = true; // Force dialog to show

      // Act
      await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(mockDialogFn).toHaveBeenCalledWith({
        data: {
          printers: mockPrinters,
          error: undefined,
          print: mockPrint,
        },
      });
    });

    it('should return the printer selected in the dialog', async () => {
      // Arrange
      platform.ANDROID = true; // Force dialog to show
      const selectedPrinter = mockPrinters[0];
      mockDialogClosedObservable.closed = of({ printer: selectedPrinter });

      // Act
      const result = await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(result).toEqual({ printer: selectedPrinter });
    });

    it('should return undefined printer when dialog is cancelled', async () => {
      // Arrange
      platform.ANDROID = true; // Force dialog to show
      mockDialogClosedObservable.closed = of(undefined);

      // Act
      const result = await spectator.service.print({
        printerType: PrinterType.LABEL,
        printFn: mockPrint,
      });

      // Assert
      expect(result).toEqual({ printer: undefined });
    });
  });
});
