import { cloneDeep } from 'lodash';

import { IEno } from './types';
import { Eno } from './Eno';
import dataConstants from '../constants';
import { EnoFactory } from '../EnoFactory';

const _proto: IEno = {
  tip: 'eno-tip',
  sid: 'eno-sid',
  clientT: {
    branch: dataConstants.BRANCH_MASTER,
    sequence: 1,
    createdDate: 1535331065704,
    modifiedDate: 1535331065704
  },
  serverT: {
    branch: dataConstants.BRANCH_MASTER,
    sequence: 1,
    session: 'eno-session',
    error: [],
    createdDate: 1535331065704,
    modifiedDate: 1535331065704
  },
  source: {
    deleted: false,
    type: 'eno-type',
    security: dataConstants.SECURITY.EVERYONE,
    parent: ['parent-sid'],
    nonce: 'eno-nonce',
    field: [
      { tip: 'field-tip_value-empty', value: [] },
      { tip: 'field-tip_value-single', value: ['field-value_value-single'] },
      { tip: 'field-tip_value-multiple', value: ['field-value-1_value-multiple', 'field-value-2_value-multiple', '10'] },
      { tip: 'field-tip_value-number', value: ['12', '33'] },
      { tip: 'field-tip_value-number-zero', value: ['0'] },
      { tip: 'field-tip-1_value-boolean', value: ['true', 'false'] },
      { tip: 'field-tip-2_value-boolean', value: ['true', 'true'] },
      {
        tip: 'field-tip_i18n-default',
        i18n: [{ lang: dataConstants.LANG_DEFAULT, value: ['field-value-1_i18n-default', 'field-value-2_i18n-default'] }]
      },
      { tip: 'field-tip_i18n-non-default', i18n: [{ lang: 'en-gb', value: ['field-value_i18n-non-default'] }] },
      {
        tip: 'field-tip_i18n-mixed',
        i18n: [
          { lang: 'en-gb', value: ['field-value_i18n-mixed_en-gb'] },
          { lang: dataConstants.LANG_DEFAULT, value: ['field-value_i18n-mixed_default'] }]
      },
      { tip: 'field-tip_i18n-number', i18n: [{ lang: dataConstants.LANG_DEFAULT, value: ['6', '54'] }] },
      { tip: 'field-tip_i18n-boolean', i18n: [{ lang: dataConstants.LANG_DEFAULT, value: ['true'] }] },
      { tip: 'field-tip_value-json', value: [JSON.stringify({key: 'field-value_value-json'})] },
      { tip: 'field-tip_value-json-throw', value: ['"key":"field-value_value-json-throw"}'] },
    ]
  }
};

const _acknowledgementEno: IEno = {
  tip: 'eno-tip',
  sid: 'eno-sid',
  serverT: {
    branch: dataConstants.BRANCH_MASTER,
    sequence: 1,
    session: 'eno-session',
    error: [],
    createdDate: 1535331065704,
    modifiedDate: 1535331065704
  }
};

const _protoWithFunction: IEno = {
  tip: 'eno-tip',
  sid: 'eno-sid',
  serverT: {
    branch: dataConstants.BRANCH_MASTER,
    sequence: 1,
    session: 'eno-session',
    error: [],
    createdDate: 1535331065704,
    modifiedDate: 1535331065704
  }
};

const _protoWithoutServerT: IEno = {
  tip: 'eno-tip',
  sid: 'eno-sid',
  clientT: {
    branch: 'some branch',
    sequence: 1,
    createdDate: 1535331065704,
    modifiedDate: 1535331065704
  }
};

const _protoWithoutTransaction: IEno = {
  tip: 'eno-tip',
  sid: 'eno-sid'
};

describe('Eno class', () => {
  it('should construct an Eno instance correctly given a prototype (object literal)', () => {
    const eno = new Eno(_proto);

    expect(eno.tip).toEqual(_proto.tip);
    expect(eno.sid).toEqual(_proto.sid);
    expect(eno.clientT).toEqual(_proto.clientT);
    expect(eno.source).toEqual(_proto.source);
  });

  it('should construct an Eno instance correctly given a prototype (Eno instance)', () => {
    const proto = new Eno(_proto);
    const eno = new Eno(proto);

    expect(eno.tip).toEqual(proto.tip);
    expect(eno.sid).toEqual(proto.sid);
    expect(eno.clientT).toEqual(proto.clientT);
    expect(eno.source).toEqual(proto.source);
  });

  it('should work correctly with method getType', () => {
    const eno = new Eno(_proto);

    expect(eno.getType()).toBe('eno-type');

    const acknowledgementEno = new Eno(_acknowledgementEno);

    expect(acknowledgementEno.getType()).toBeNull();
  });

  it('should remove any functions in the given proto', () => {
    const eno = new Eno(_proto);

    expect(eno.getType()).toBe('eno-type');

    const acknowledgementEno = new Eno(_acknowledgementEno);

    expect(acknowledgementEno.getType()).toBeNull();
  });

  it('#toJson() should work correctly', () => {
    const eno = new Eno(_proto);

    expect(eno.toJson()).toEqual(_proto);
  });

  it('#toJson() should work correctly with whitelist', () => {
    const eno = new Eno(_proto);

    const jsonEno = eno.toJson({propWhiteList: ['tip', 'source']});

    expect(jsonEno.sid).toBeUndefined();
    expect(jsonEno.clientT).toBeUndefined();
    expect(jsonEno.serverT).toBeUndefined();
    expect(jsonEno.tip).not.toBeUndefined();
    expect(jsonEno.source).not.toBeUndefined();
  });

  it('#getBranch() should work', () => {
    const eno = new Eno(_proto);

    expect(eno.getBranch()).toBe(dataConstants.BRANCH_MASTER);
  });

  it('#getBranch() should work', () => {
    const eno = new Eno(_protoWithoutServerT);

    expect(eno.getBranch()).toBe('some branch');
  });

  it('#getBranch() should work', () => {
    const eno = new Eno(_protoWithoutTransaction);

    expect(eno.getBranch()).toBeNull();
  });

  it('should work correctly with method getSession', () => {
    const eno = new Eno(_proto);

    expect(eno.getSessionId()).toBe('eno-session');

    const protoWithoutServerT = cloneDeep(_proto);
    protoWithoutServerT.serverT = null;
    const enoWithoutServerT = new Eno(protoWithoutServerT);

    expect(enoWithoutServerT.getSessionId()).toBeNull();
  });

  it('should return empty array on non-existing field tip with method getFieldValues', () => {
    const eno = new Eno(_proto);

    expect(eno.getFieldValues('field-tip-wrong')).toEqual([]);
  });

  it('should work correctly with method hasError', () => {
    const enoWithoutError = new Eno(_proto);

    expect(enoWithoutError.hasError()).toBeFalsy();

    const protoWithError = cloneDeep(_proto);
    protoWithError.serverT.error.push('errorSid1');
    const enoWithError = new Eno(protoWithError);

    expect(enoWithError.hasError()).toBeTruthy();
  });

  it('should work correctly with method getFieldValues', () => {
    const eno = new Eno(_proto);

    // Value.
    expect(eno.getFieldValues('field-tip_value-single')).toEqual(
      ['field-value_value-single'],
      '[getFieldValues] Value (single).'
    );
    expect(eno.getFieldValues('field-tip_value-multiple')).toEqual(
      ['field-value-1_value-multiple', 'field-value-2_value-multiple', '10'],
      '[getFieldValues] Value (multiple).'
    );

    // I18n with the default language.
    expect(eno.getFieldValues('field-tip_i18n-default')).toEqual(
      ['field-value-1_i18n-default', 'field-value-2_i18n-default'],
      '[getFieldValues] I18n (default language).'
    );
    expect(eno.getFieldValues('field-tip_i18n-default', dataConstants.LANG_DEFAULT)).toEqual(
      ['field-value-1_i18n-default', 'field-value-2_i18n-default'],
      '[getFieldValues] I18n (default language) (explicitly passed).'
    );
    expect(eno.getFieldValues('field-tip_i18n-default', 'en-gb')).toEqual(
      ['field-value-1_i18n-default', 'field-value-2_i18n-default'],
      '[getFieldValues] I18n (default language) (language guessing).'
    );
    expect(eno.getFieldValues('field-tip_i18n-default', 'zh-cn')).toEqual(
      ['field-value-1_i18n-default', 'field-value-2_i18n-default'],
      '[getFieldValues] I18n (default language) (language fallback).'
    );

    // I18n with a non-default language.
    expect(eno.getFieldValues('field-tip_i18n-non-default')).toEqual(
      ['field-value_i18n-non-default'],
      '[getFieldValues] I18n (non-default language).'
    );
    expect(eno.getFieldValues('field-tip_i18n-non-default', dataConstants.LANG_DEFAULT)).toEqual(
      ['field-value_i18n-non-default'],
      '[getFieldValues] I18n (non-default language) (language guessing).'
    );
    expect(eno.getFieldValues('field-tip_i18n-non-default', 'en-gb')).toEqual(
      ['field-value_i18n-non-default'],
      '[getFieldValues] I18n (non-default language) (explicitly passed).'
    );
    expect(eno.getFieldValues('field-tip_i18n-non-default', 'zh-cn')).toEqual(
      [],
      '[getFieldValues] I18n (non-default language) (not found).');

    // I18n with multiple languages.
    expect(eno.getFieldValues('field-tip_i18n-mixed')).toEqual(
      ['field-value_i18n-mixed_default'],
      '[getFieldValues] I18n (multiple languages).'
    );
    expect(eno.getFieldValues('field-tip_i18n-mixed', dataConstants.LANG_DEFAULT)).toEqual(
      ['field-value_i18n-mixed_default'],
      '[getFieldValues] I18n (multiple languages) (language guessing).'
    );
    expect(eno.getFieldValues('field-tip_i18n-mixed', 'en-gb')).toEqual(
      ['field-value_i18n-mixed_en-gb'],
      '[getFieldValues] I18n (multiple languages) (explicitly passed).'
    );
    expect(eno.getFieldValues('field-tip_i18n-mixed', 'zh-cn')).toEqual(
      ['field-value_i18n-mixed_default'],
      '[getFieldValues] I18n (multiple languages) (language fallback).');
  });

  it('should work correctly with method getFieldStringValue', () => {
    const eno = new Eno(_proto);

    expect(eno.getFieldStringValue('field-tip_value-single')).toEqual(
      'field-value_value-single',
      '[getFieldStringValue] Value (single).'
    );
    expect(eno.getFieldStringValue('field-tip_value-multiple')).toEqual(
      'field-value-1_value-multiple, field-value-2_value-multiple, 10',
      '[getFieldStringValue] Value (multiple).'
    );
    expect(eno.getFieldStringValue('field-tip_i18n-default')).toEqual(
      'field-value-1_i18n-default, field-value-2_i18n-default',
      '[getFieldStringValue] I18n (default language).'
    );
    expect(eno.getFieldStringValue('field-tip_i18n-non-default', 'en-gb')).toEqual(
      'field-value_i18n-non-default',
      '[getFieldStringValue] I18n (non-default language) (explicitly passed).'
    );
    expect(eno.getFieldStringValue('field-tip_i18n-non-default', 'zh-cn')).toEqual(
      null,
      '[getFieldStringValue] I18n (non-default language) (not found).');
  });

  it('should work correctly with method getFieldNumberValue', () => {
    const eno = new Eno(_proto);
    expect(eno.getFieldNumberValue('field-tip_value-empty')).toEqual(
      null,
      '[getFieldNumberValue] Value (empty).'
    );
    expect(eno.getFieldNumberValue('field-tip_value-single')).toEqual(
      null,
      '[getFieldNumberValue] Value (single).'
    );
    expect(eno.getFieldNumberValue('field-tip_value-multiple')).toEqual(
      null,
      '[getFieldNumberValue] Value (multiple).'
    );
    expect(eno.getFieldNumberValue('field-tip_value-number')).toEqual(
      12,
      '[getFieldNumberValue] Value (number values).'
    );
    expect(eno.getFieldNumberValue('field-tip_value-number-zero')).toEqual(
      0,
      '[getFieldNumberValue] Value (zero value).'
    );
    expect(eno.getFieldNumberValue('field-tip_i18n-number')).toEqual(
      6,
      '[getFieldNumberValue] I18n (number values).'
    );
    expect(eno.getFieldNumberValue('field-tip_i18n-number', dataConstants.LANG_DEFAULT)).toEqual(
      6,
      '[getFieldNumberValue] I18n (number values) (explicitly passed).'
    );
  });

  it('should work correctly with method getFieldBooleanValue', () => {
    const eno = new Eno(_proto);

    expect(eno.getFieldBooleanValue('field-tip_value-single')).toEqual(
      false,
      '[getFieldBooleanValue] Value (single).'
    );
    expect(eno.getFieldBooleanValue('field-tip_value-multiple')).toEqual(
      false,
      '[getFieldBooleanValue] Value (multiple).'
    );
    expect(eno.getFieldBooleanValue('field-tip-1_value-boolean')).toEqual(
      false,
      '[getFieldBooleanValue] Value (boolean value 1).'
    );
    expect(eno.getFieldBooleanValue('field-tip-2_value-boolean')).toEqual(
      true,
      '[getFieldBooleanValue] I18n (boolean value 2).'
    );
    expect(eno.getFieldBooleanValue('field-tip_i18n-boolean')).toEqual(
      true,
      '[getFieldBooleanValue] I18n (boolean value).'
    );
    expect(eno.getFieldBooleanValue('field-tip_i18n-boolean', dataConstants.LANG_DEFAULT)).toEqual(
      true,
      '[getFieldBooleanValue] I18n (boolean value) (explicitly passed).'
    );
    expect(eno.getFieldBooleanValue('field-tip_empty', dataConstants.LANG_DEFAULT)).toEqual(
      false,
      '[getFieldBooleanValue] No values.'
    );
  });

  it('should work correctly with method getFieldJsonValue', () => {
    const eno = new Eno(_proto);

    expect(eno.getFieldJsonValue('field-tip_value-json')).toEqual(
      {key: 'field-value_value-json'}
    );
    expect(() => eno.getFieldJsonValue('field-tip_value-json-throw')).toThrow();
  });

  it('#getFieldJsonValue should return null on non-existing field tip', () => {
    const eno = new Eno(_proto);

    expect(eno.getFieldJsonValue('non-existing field tip')).toBeNull();
  });

  it('should work correctly with method getFieldRawI18n', () => {
    const eno = new Eno(_proto);

    expect(eno.getFieldRawI18n('field-tip_i18n-mixed')).toEqual(
      [
        { lang: 'en-gb', value: ['field-value_i18n-mixed_en-gb'] },
        { lang: dataConstants.LANG_DEFAULT, value: ['field-value_i18n-mixed_default'] }
      ]
    );

    expect(eno.getFieldRawI18n('field-tip_value-multiple')).toEqual(
      [
        { lang: dataConstants.LANG_DEFAULT, value: ['field-value-1_value-multiple', 'field-value-2_value-multiple', '10'] }
      ]
    );
    
    expect(eno.getFieldRawI18n('field-tip_value-single')).toEqual(
      [
        { lang: dataConstants.LANG_DEFAULT, value: ['field-value_value-single'] }
      ]
    );
  });

  describe('#isSourceDiff', () => {
    let enoFactory: EnoFactory;

    beforeAll(() => {
      enoFactory = new EnoFactory('type', 'security');
    });

    beforeEach(() => {
      enoFactory.reset('type', 'security');
    });

    it('Same eno', () => {
      const eno1: Eno = enoFactory.setFields([
        { tip: 'field1', value: ['f1v1'] },
        { tip: 'field2', value: ['f2v1', 'f2v2'] },
      ]).makeEno();
      const eno2: Eno = cloneDeep(eno1);

      expect(eno1.isContentDiff(eno2)).toBeFalsy();
    });

    it('#isSourceDiff - difference from delete', () => {
      const eno1: Eno = enoFactory.setFields([
        { tip: 'field1', value: ['f1v1'] },
        { tip: 'field2', value: ['f2v1', 'f2v2'] },
      ]).makeEno();
      const eno2: Eno = enoFactory.setDeleted(true).makeEno();

      expect(eno1.isContentDiff(eno2)).toBeTruthy();
    });

    it('#isSourceDiff - difference from type', () => {
      const eno1: Eno = enoFactory.setFields([
        { tip: 'field1', value: ['f1v1'] },
        { tip: 'field2', value: ['f2v1', 'f2v2'] },
      ]).makeEno();
      const eno2: Eno = enoFactory.setType('type2').makeEno();

      expect(eno1.isContentDiff(eno2)).toBeTruthy();
    });

    it('#isSourceDiff - difference from security', () => {
      const eno1: Eno = enoFactory.setFields([
        { tip: 'field1', value: ['f1v1'] },
        { tip: 'field2', value: ['f2v1', 'f2v2'] },
      ]).makeEno();
      const eno2: Eno = enoFactory.setSecurity('security2').makeEno();

      expect(eno1.isContentDiff(eno2)).toBeTruthy();
    });

    it('#isSourceDiff - difference from field', () => {
      const eno1: Eno = enoFactory.setFields([
        { tip: 'field1', value: ['f1v1'] },
        { tip: 'field2', value: ['f2v1', 'f2v2'] },
      ]).makeEno();
      const eno2: Eno = enoFactory.setFields([
        { tip: 'field1', value: ['f1v1'] },
        { tip: 'field2', value: ['f2v1'] }
      ]).makeEno();
      const eno3: Eno = enoFactory.setFields([
        { tip: 'field1', value: ['f1v1'] },
        { tip: 'field2', value: ['f2v1', 'f2v2'] },
        { tip: 'field3', value: ['f3v1'] }
      ]).makeEno();

      expect(eno1.isContentDiff(eno2)).toBeTruthy('field value changed');
      expect(eno1.isContentDiff(eno3)).toBeTruthy('field value added');
    });
  });
});
