import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import {
  ScannerService,
  SCANDIT_LICENSE,
  SCANDIT_LIBRARY_LOCATION,
  SCANDIT_DEFAULT_SYMBOLOGIES,
} from './scanner.service';
import { Platform } from '@angular/cdk/platform';
import { configure } from 'scandit-web-datacapture-core';

import { Config, CONFIG_DATA } from '@isa/core/config'; // <-- Use the real tokens

class MockConfig {
  get(key: string) {
    if (key === 'licence.scandit') return 'FAKE-LICENSE';
    return undefined;
  }
}

jest.mock('scandit-web-datacapture-core', () => ({
  configure: jest.fn(),
}));
jest.mock('scandit-web-datacapture-barcode', () => ({
  barcodeCaptureLoader: jest.fn(() => 'mockedBarcodeLoader'),
}));
const loggerInfo = jest.fn();
const loggerError = jest.fn();
jest.mock('@isa/core/logging', () => ({
  logger: jest.fn(() => ({
    info: loggerInfo,
    error: loggerError,
  })),
}));

/**
 * Unit tests for the ScannerService.
 * Tests initialization, configuration, and scanning functionality.
 */
describe('ScannerService', () => {
  let spectator: SpectatorService<ScannerService>;

  const createService = createServiceFactory({
    service: ScannerService,
    providers: [
      { provide: SCANDIT_LICENSE, useValue: 'FAKE-LICENSE' },
      { provide: SCANDIT_LIBRARY_LOCATION, useValue: 'https://fake-location/' },
      { provide: SCANDIT_DEFAULT_SYMBOLOGIES, useValue: ['ean8'] },
      { provide: Platform, useValue: { IOS: true, ANDROID: false } },
      { provide: CONFIG_DATA, useValue: {} },
      { provide: Config, useClass: MockConfig },
    ],
  });

  beforeEach(() => {
    jest.clearAllMocks();
    spectator = createService();
  });

  describe('configure', () => {
    it('should configure successfully on supported platform', async () => {
      (configure as jest.Mock).mockResolvedValue(undefined);

      await spectator.service.configure();

      expect(configure).toHaveBeenCalledWith({
        licenseKey: 'FAKE-LICENSE',
        libraryLocation: 'https://fake-location/',
        moduleLoaders: ['mockedBarcodeLoader'],
      });
      expect(loggerInfo).toHaveBeenCalledWith('Scanner initializing');
      expect(loggerInfo).toHaveBeenCalledWith('Scanner ready');
    });

    it('should not re-configure if already initializing or ready', async () => {
      (configure as jest.Mock).mockResolvedValue(undefined);

      await spectator.service.configure();
      await spectator.service.configure();

      expect((configure as jest.Mock).mock.calls.length).toBe(1);
    });

    it('should throw and log error if configure throws', async () => {
      (configure as jest.Mock).mockRejectedValue(new Error('Config failed'));

      await expect(spectator.service.configure()).rejects.toThrow(
        'Config failed',
      );
      expect(loggerError).toHaveBeenCalledWith(
        'Failed to configure Scandit',
        expect.any(Error),
      );
    });
  });

  describe('ready', () => {
    it('should return true if status is Ready', async () => {
      (configure as jest.Mock).mockResolvedValue(undefined);
      await spectator.service.configure();
      expect(spectator.service.ready()).toBe(true);
    });

    it('should return false if status is not Ready', () => {
      expect(spectator.service.ready()).toBe(false);
    });
  });

  // Additional tests for edge cases and error handling

  it('should set status to Error if configure throws', async () => {
    (configure as jest.Mock).mockRejectedValue(new Error('fail'));
    try {
      await spectator.service.configure();
    } catch {
      // ignore
    }
    expect(spectator.service.ready()).toBe(false);
    expect(loggerError).toHaveBeenCalledWith(
      'Failed to configure Scandit',
      expect.any(Error),
    );
  });
});
