import { configureTestBed } from '~/testing/unit-test-helper';
import { DimlessBinaryPipe } from '../pipes/dimless-binary.pipe';
import { DimlessPipe } from '../pipes/dimless.pipe';
import { FormatterService } from './formatter.service';

describe('FormatterService', () => {
  let service: FormatterService;
  let dimlessBinaryPipe: DimlessBinaryPipe;
  let dimlessPipe: DimlessPipe;

  const convertToBytesAndBack = (value: string, newValue?: string) => {
    expect(dimlessBinaryPipe.transform(service.toBytes(value))).toBe(newValue || value);
  };

  configureTestBed({
    providers: [FormatterService, DimlessBinaryPipe]
  });

  beforeEach(() => {
    service = new FormatterService();
    dimlessBinaryPipe = new DimlessBinaryPipe(service);
    dimlessPipe = new DimlessPipe(service);
  });

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

  describe('format_number', () => {
    const formats = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];

    it('should return minus for unsupported values', () => {
      expect(service.format_number(service, 1024, formats)).toBe('-');
      expect(service.format_number(undefined, 1024, formats)).toBe('-');
      expect(service.format_number(null, 1024, formats)).toBe('-');
    });

    it('should test some values', () => {
      expect(service.format_number('0', 1024, formats)).toBe('0 B');
      expect(service.format_number('0.1', 1024, formats)).toBe('0.1 B');
      expect(service.format_number('1.2', 1024, formats)).toBe('1.2 B');
      expect(service.format_number('1', 1024, formats)).toBe('1 B');
      expect(service.format_number('1024', 1024, formats)).toBe('1 KiB');
      expect(service.format_number(23.45678 * Math.pow(1024, 3), 1024, formats)).toBe('23.5 GiB');
      expect(service.format_number(23.45678 * Math.pow(1024, 3), 1024, formats, 2)).toBe(
        '23.46 GiB'
      );
    });

    it('should test some dimless values', () => {
      expect(dimlessPipe.transform(0.6)).toBe('0.6');
      expect(dimlessPipe.transform(1000.608)).toBe('1 k');
      expect(dimlessPipe.transform(1e10)).toBe('10 G');
      expect(dimlessPipe.transform(2.37e16)).toBe('23.7 P');
    });
  });

  describe('formatNumberFromTo', () => {
    const formats = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
    const formats2 = ['ns', 'μs', 'ms', 's'];

    it('should test some values and data units', () => {
      expect(service.formatNumberFromTo('0.1', 'B', 'TiB', 1024, formats)).toBe('0 TiB');
      expect(service.formatNumberFromTo('1024', 'B', 'KiB', 1024, formats)).toBe('1 KiB');
      expect(service.formatNumberFromTo(1000, 'mib', 'gib', 1024, formats, 3)).toBe('0.977 gib');
      expect(service.formatNumberFromTo(1024, 'GiB', 'MiB', 1024, formats)).toBe('1048576 MiB');
      expect(
        service.formatNumberFromTo(23.45678 * Math.pow(1024, 3), 'B', 'GiB', 1024, formats)
      ).toBe('23.5 GiB');
      expect(
        service.formatNumberFromTo(23.45678 * Math.pow(1024, 3), 'B', 'GiB', 1024, formats, 2)
      ).toBe('23.46 GiB');

      expect(service.formatNumberFromTo('128', 'ns', 'ms', 1000, formats2)).toBe('0 ms');
      expect(service.formatNumberFromTo(128, 'ns', 'ms', 1000, formats2, 4)).toBe('0.0001 ms');
      expect(service.formatNumberFromTo(20, 's', 'ms', 1000, formats2, 4)).toBe('20000 ms');
    });
  });

  describe('toBytes', () => {
    it('should not convert wrong values', () => {
      expect(service.toBytes('10xyz')).toBeNull();
      expect(service.toBytes('1.1.1KiB')).toBeNull();
      expect(service.toBytes('1.1 KiloByte')).toBeNull();
      expect(service.toBytes('1.1  kib')).toBeNull();
      expect(service.toBytes('1.kib')).toBeNull();
      expect(service.toBytes('1 ki')).toBeNull();
      expect(service.toBytes(undefined)).toBeNull();
      expect(service.toBytes('')).toBeNull();
      expect(service.toBytes('-')).toBeNull();
      expect(service.toBytes(null)).toBeNull();
    });

    it('should convert values to bytes', () => {
      expect(service.toBytes('4815162342')).toBe(4815162342);
      expect(service.toBytes('100M')).toBe(104857600);
      expect(service.toBytes('100 M')).toBe(104857600);
      expect(service.toBytes('100 mIb')).toBe(104857600);
      expect(service.toBytes('100 mb')).toBe(104857600);
      expect(service.toBytes('100MIB')).toBe(104857600);
      expect(service.toBytes('1.532KiB')).toBe(Math.round(1.532 * 1024));
      expect(service.toBytes('0.000000000001TiB')).toBe(1);
    });

    it('should convert values to human readable again', () => {
      convertToBytesAndBack('1.1 MiB');
      convertToBytesAndBack('1.0MiB', '1 MiB');
      convertToBytesAndBack('8.9 GiB');
      convertToBytesAndBack('123.5 EiB');
    });
  });

  describe('formatToBinary', () => {
    it('should return formatted string when split=false (default decimals=1)', () => {
      expect(service.formatToBinary('0', false)).toBe('0 B');
      expect(service.formatToBinary('0.1', false)).toBe('0.1 B');
      expect(service.formatToBinary('1', false)).toBe('1 B');
      expect(service.formatToBinary('1024', false)).toBe('1 KiB');
      expect(service.formatToBinary(23.45678 * Math.pow(1024, 3), false)).toBe('23.5 GiB');
    });

    it('should respect decimals param when split=false', () => {
      expect(service.formatToBinary(23.45678 * Math.pow(1024, 3), false, 2)).toBe('23.46 GiB');
      expect(service.formatToBinary('1024', false, 3)).toBe('1 KiB');
    });

    it('should return tuple [number, unit] when split=true', () => {
      expect(service.formatToBinary('0', true)).toEqual([0, 'B']);
      expect(service.formatToBinary('1024', true)).toEqual([1, 'KiB']);
      expect(service.formatToBinary(23.45678 * Math.pow(1024, 3), true)).toEqual([23.5, 'GiB']);
    });

    it('should return "-" for unsupported values when split=false', () => {
      expect(service.formatToBinary(undefined as any, false)).toBe('-');
      expect(service.formatToBinary(null as any, false)).toBe('-');
      expect(service.formatToBinary(service as any, false)).toBe('-');
    });

    it('should return a safe tuple when split=true and input is unsupported', () => {
      expect(service.formatToBinary(undefined as any, true)).toEqual([NaN, 'B']);
      expect(service.formatToBinary(null as any, true)).toEqual([NaN, 'B']);
      expect(service.formatToBinary(service as any, true)).toEqual([NaN, 'B']);
    });
  });

  describe('convertToUnit', () => {
    it('should return 0 for empty-ish values', () => {
      expect(service.convertToUnit('', 'B', 'KiB')).toBe(0);
      expect(service.convertToUnit(undefined as any, 'B', 'KiB')).toBe(0);
      expect(service.convertToUnit(null as any, 'B', 'KiB')).toBe(0);
    });

    it('should convert between binary units (default decimals=1)', () => {
      expect(service.convertToUnit('1024', 'B', 'KiB')).toBe(1);
      expect(service.convertToUnit('1', 'GiB', 'MiB')).toBe(1024);
      expect(service.convertToUnit('1', 'MiB', 'KiB')).toBe(1024);
    });

    it('should respect decimals rounding', () => {
      // 1000 MiB -> 0.9765625 GiB -> with 3 decimals => 0.977
      expect(service.convertToUnit('1000', 'mib', 'gib', 3)).toBe(0.977);

      // with 1 decimal => 1.0 (rounding)
      expect(service.convertToUnit('1000', 'mib', 'gib', 1)).toBe(1);
    });

    it('should handle very small conversions that round to 0', () => {
      expect(service.convertToUnit('0.1', 'B', 'TiB')).toBe(0);
    });
  });

  describe('convertToNumber', () => {
    it('should remove commas and trim', () => {
      expect(service.convertToNumber('1,024')).toBe(1024);
      expect(service.convertToNumber('  23.5  ')).toBe(23.5);
    });
  });
});
