import { expect } from 'chai';
import * as proxyquire from 'proxyquire';
import {
  localStorage,
  ILocalStorage,
  ILocalStorageChangedEvent,
  LocalStoragePropertyFactory,
} from '.';

let mockData = {};
const storeMock = {
  init(): ILocalStorage {
    return {
      async getItem(key: string) {
        return mockData[key];
      },
      async setItem(key: string, value: any) {
        mockData[key] = value;
      },
      async removeItem(key: string) {
        delete mockData[key];
      },
    };
  },
};

const { prop }: { prop: LocalStoragePropertyFactory } = proxyquire('./prop', {
  './store.web': storeMock,
});

describe('local-storage', () => {
  beforeEach(() => {
    mockData = {};
  });

  it('returns a function', () => {
    const myProp = prop('MY_KEY');
    expect(myProp).to.be.an.instanceof(Function);
  });

  it('stores the key on the function', () => {
    const myProp = prop('MY_KEY');
    expect(myProp.key).to.eql('MY_KEY');
  });

  it('sets the [isProp] flag on the property function', () => {
    const myProp = prop('MY_KEY');
    expect(myProp.isProp).to.eql(true);
  });

  describe('default value', () => {
    it('has no default value', async () => {
      const myProp = prop('MY_PROP');
      expect(await myProp()).to.eql(undefined);
    });

    it('has a curried default property', async () => {
      const myProp = prop('MY_PROP', { default: 'My Default' });
      expect(await myProp()).to.eql('My Default');
    });

    it('overrides the curried property value', async () => {
      const myProp = prop('MY_PROP', { default: 'My Curried value' });
      const result = await myProp(undefined, { default: 'Overridden' });
      expect(result).to.eql('Overridden');
    });
  });

  describe('nothing (no value)', () => {
    it('undefined', async () => {
      const myProp = prop('UNDEFINED_KEY');
      expect(await myProp()).to.eql(undefined);
    });

    it('null - reset the value', async () => {
      const myProp = prop<string>('NULL_KEY');
      expect(await myProp(undefined, { default: null })).to.equal(null);
      myProp('myValue');
      expect(await myProp()).to.equal('myValue');
      myProp(null);
      expect(await myProp()).to.equal(undefined);
    });
  });

  describe('data types', () => {
    it('boolean', async () => {
      const myProp = prop<boolean>('BOOL_KEY', { default: false });
      expect(await myProp(undefined)).to.equal(false);
      expect(await myProp(undefined, { default: true })).to.equal(true);
      myProp(true);
      expect(await myProp()).to.equal(true);
      myProp(false);
      expect(await myProp()).to.equal(false);
    });

    it('string', async () => {
      const myProp = prop<string>('STRING_KEY', { default: 'myDefault' });
      expect(await myProp(undefined)).to.equal('myDefault');
      myProp('myValue');
      expect(await myProp()).to.equal('myValue');
    });

    it('number', async () => {
      const myProp = prop<number>('NUMBER_KEY', { default: 321 });
      expect(await myProp()).to.equal(321);
      myProp(1.23);
      expect(await myProp()).to.equal(1.23);
    });

    it('object', async () => {
      const myProp = prop('OBJECT_KEY', { default: { value: 123 } });
      expect(await myProp()).to.eql({ value: 123 });
      myProp({ root: { child: 88 } });
      expect(await myProp()).to.eql({ root: { child: 88 } });
    });

    it('date', async () => {
      const now = new Date();
      const myProp = prop('DATE_KEY', { default: now });
      expect(await myProp()).to.eql(now);
      myProp(now);
      expect(await myProp()).to.eql(now);
    });

    it('date - iso string when on object', async () => {
      const now = new Date();
      const myProp = prop('DATE_KEY', { default: now });
      expect(await myProp()).to.eql(now);
      myProp({ now });
      expect(await myProp()).to.eql({ now: now.toISOString() });
    });

    describe('events$ (Observable)', () => {
      const myProp = prop('EVENT_PROP');
      let count = 0;
      let event: ILocalStorageChangedEvent | undefined;
      localStorage.changes$.forEach(e => {
        count += 1;
        event = e;
      });

      beforeEach(() => {
        count = 0;
        event = undefined;
      });

      it('fires write event', async () => {
        await myProp(123);

        expect(count).to.equal(1);
        expect(event).to.not.equal(undefined);
        if (event) {
          const { type, key, value } = event as ILocalStorageChangedEvent;
          expect(type).to.equal('number');
          expect(key).to.equal(myProp.key);
          expect(value).to.equal(123);
        }
      });

      it('fires write event (null)', async () => {
        await myProp(123);
        await myProp(null);

        expect(count).to.equal(2);
        expect(event).to.not.equal(undefined);
        if (event) {
          const { type, key, value } = event as ILocalStorageChangedEvent;
          expect(type).to.equal('null');
          expect(key).to.equal(myProp.key);
          expect(value).to.equal(null);
        }
      });
    });
  });
});
