import { expect } from 'chai';
import {
  longestPrefixMatch,
  reconcileSettings,
} from '../../node/monitor-settings/monitor-settings-utils';
import { PluggableMonitorSettings } from '../../node/monitor-settings/monitor-settings-provider';

type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };

describe('longestPrefixMatch', () => {
  const settings = {
    'arduino:avr:uno-port1-protocol1': {
      name: 'Arduino Uno',
    },
    'arduino:avr:due-port1-protocol2': {
      name: 'Arduino Due',
    },
  };

  it('should return the exact prefix when found', async () => {
    const prefix = 'arduino:avr:uno-port1-protocol1';

    const { matchingPrefix } = longestPrefixMatch(
      prefix,
      settings as unknown as Record<string, PluggableMonitorSettings>
    );

    expect(matchingPrefix).to.equal(prefix);
  });

  it('should return the exact object when the prefix match', async () => {
    const prefix = 'arduino:avr:uno-port1-protocol1';

    const { matchingSettings } = longestPrefixMatch(
      prefix,
      settings as unknown as Record<string, PluggableMonitorSettings>
    );

    expect(matchingSettings).to.have.property('name').to.equal('Arduino Uno');
  });

  it('should return a partial matching prefix when a similar object is found', async () => {
    const prefix = 'arduino:avr:due-port2-protocol2';

    const { matchingPrefix } = longestPrefixMatch(
      prefix,
      settings as unknown as Record<string, PluggableMonitorSettings>
    );

    expect(matchingPrefix).to.equal('arduino:avr:due');
  });

  it('should return the closest object when the prefix partially match', async () => {
    const prefix = 'arduino:avr:uno-port1-protocol2';

    const { matchingSettings } = longestPrefixMatch(
      prefix,
      settings as unknown as Record<string, PluggableMonitorSettings>
    );

    expect(matchingSettings).to.have.property('name').to.equal('Arduino Uno');
  });

  it('should return an empty matching prefix when no similar object is found', async () => {
    const prefix = 'arduino:avr:tre-port2-protocol2';

    const { matchingPrefix } = longestPrefixMatch(
      prefix,
      settings as unknown as Record<string, PluggableMonitorSettings>
    );

    expect(matchingPrefix).to.equal('');
  });

  it('should return an empty object when no similar object is found', async () => {
    const prefix = 'arduino:avr:tre-port1-protocol2';

    const { matchingSettings } = longestPrefixMatch(
      prefix,
      settings as unknown as Record<string, PluggableMonitorSettings>
    );

    expect(matchingSettings).to.be.empty;
  });
});

describe('reconcileSettings', () => {
  const defaultSettings = {
    setting1: {
      id: 'setting1',
      label: 'Label setting1',
      type: 'enum',
      values: ['a', 'b', 'c'],
      selectedValue: 'b',
    },
    setting2: {
      id: 'setting2',
      label: 'Label setting2',
      type: 'enum',
      values: ['a', 'b', 'c'],
      selectedValue: 'b',
    },
    setting3: {
      id: 'setting3',
      label: 'Label setting3',
      type: 'enum',
      values: ['a', 'b', 'c'],
      selectedValue: 'b',
    },
  };

  it('should return default settings if new settings are missing', async () => {
    const newSettings: PluggableMonitorSettings = {};

    const reconciledSettings = reconcileSettings(newSettings, defaultSettings);

    expect(reconciledSettings).to.deep.equal(defaultSettings);
  });

  it('should add missing attributes copying it from the default settings', async () => {
    const newSettings: PluggableMonitorSettings = JSON.parse(
      JSON.stringify(defaultSettings)
    );
    delete newSettings.setting2;

    const reconciledSettings = reconcileSettings(newSettings, defaultSettings);

    expect(reconciledSettings).to.have.property('setting2');
  });
  it('should remove wrong settings attributes using the default settings as a reference', async () => {
    const newSettings: PluggableMonitorSettings = JSON.parse(
      JSON.stringify(defaultSettings)
    );
    newSettings['setting4'] = defaultSettings.setting3;

    const reconciledSettings = reconcileSettings(newSettings, defaultSettings);

    expect(reconciledSettings).not.to.have.property('setting4');
  });
  it('should reset non-value fields to those defined in the default settings', async () => {
    const newSettings: DeepWriteable<PluggableMonitorSettings> = JSON.parse(
      JSON.stringify(defaultSettings)
    );
    newSettings['setting2'].id = 'fake id';

    const reconciledSettings = reconcileSettings(newSettings, defaultSettings);

    expect(reconciledSettings.setting2)
      .to.have.property('id')
      .equal('setting2');
  });
  it('should accept a selectedValue if it is a valid one', async () => {
    const newSettings: PluggableMonitorSettings = JSON.parse(
      JSON.stringify(defaultSettings)
    );
    newSettings.setting2.selectedValue = 'c';

    const reconciledSettings = reconcileSettings(newSettings, defaultSettings);

    expect(reconciledSettings.setting2)
      .to.have.property('selectedValue')
      .to.equal('c');
  });
  it('should fall a back to the first valid setting when the selectedValue is not valid', async () => {
    const newSettings: PluggableMonitorSettings = JSON.parse(
      JSON.stringify(defaultSettings)
    );
    newSettings.setting2.selectedValue = 'z';

    const reconciledSettings = reconcileSettings(newSettings, defaultSettings);

    expect(reconciledSettings.setting2)
      .to.have.property('selectedValue')
      .to.equal('a');
  });
  it('should accept any value if default values are not set', async () => {
    const wrongDefaults: DeepWriteable<PluggableMonitorSettings> = JSON.parse(
      JSON.stringify(defaultSettings)
    );
    wrongDefaults.setting2.values = [];

    const newSettings: PluggableMonitorSettings = JSON.parse(
      JSON.stringify(wrongDefaults)
    );
    newSettings.setting2.selectedValue = 'z';

    const reconciledSettings = reconcileSettings(newSettings, wrongDefaults);

    expect(reconciledSettings.setting2)
      .to.have.property('selectedValue')
      .to.equal('z');
  });
});