const PushWorker = require('../lib').PushWorker;
const PushUtils = require('../lib/Push/utils');
const Config = require('../lib/Config');
const { pushStatusHandler } = require('../lib/StatusHandler');
const rest = require('../lib/rest');

describe('PushWorker', () => {
  it('should run with small batch', done => {
    const batchSize = 3;
    let sendCount = 0;
    reconfigureServer({
      push: {
        queueOptions: {
          disablePushWorker: true,
          batchSize,
        },
      },
    })
      .then(() => {
        expect(Config.get('test').pushWorker).toBeUndefined();
        new PushWorker({
          send: (body, installations) => {
            expect(installations.length <= batchSize).toBe(true);
            sendCount += installations.length;
            return Promise.resolve();
          },
          getValidPushTypes: function () {
            return ['ios', 'android'];
          },
        });
        const installations = [];
        while (installations.length != 10) {
          const installation = new Parse.Object('_Installation');
          installation.set('installationId', 'installation_' + installations.length);
          installation.set('deviceToken', 'device_token_' + installations.length);
          installation.set('badge', 1);
          installation.set('deviceType', 'ios');
          installations.push(installation);
        }
        return Parse.Object.saveAll(installations);
      })
      .then(() => {
        return Parse.Push.send(
          {
            where: {
              deviceType: 'ios',
            },
            data: {
              alert: 'Hello world!',
            },
          },
          { useMasterKey: true }
        );
      })
      .then(() => {
        return new Promise(resolve => {
          setTimeout(resolve, 500);
        });
      })
      .then(() => {
        expect(sendCount).toBe(10);
        done();
      })
      .catch(err => {
        jfail(err);
      });
  });

  describe('localized push', () => {
    it('should return locales', () => {
      const locales = PushUtils.getLocalesFromPush({
        data: {
          'alert-fr': 'french',
          alert: 'Yo!',
          'alert-en-US': 'English',
        },
      });
      expect(locales).toEqual(['fr', 'en-US']);
    });

    it('should return and empty array if no locale is set', () => {
      const locales = PushUtils.getLocalesFromPush({
        data: {
          alert: 'Yo!',
        },
      });
      expect(locales).toEqual([]);
    });

    it('should deduplicate locales', () => {
      const locales = PushUtils.getLocalesFromPush({
        data: {
          alert: 'Yo!',
          'alert-fr': 'french',
          'title-fr': 'french',
        },
      });
      expect(locales).toEqual(['fr']);
    });

    it('should handle empty body data', () => {
      expect(PushUtils.getLocalesFromPush({})).toEqual([]);
    });

    it('transforms body appropriately', () => {
      const cleanBody = PushUtils.transformPushBodyForLocale(
        {
          data: {
            alert: 'Yo!',
            'alert-fr': 'frenchy!',
            'alert-en': 'english',
          },
        },
        'fr'
      );
      expect(cleanBody).toEqual({
        data: {
          alert: 'frenchy!',
        },
      });
    });

    it('transforms body appropriately with title locale', () => {
      const cleanBody = PushUtils.transformPushBodyForLocale(
        {
          data: {
            alert: 'Yo!',
            'alert-fr': 'frenchy!',
            'alert-en': 'english',
            'title-fr': 'french title',
          },
        },
        'fr'
      );
      expect(cleanBody).toEqual({
        data: {
          alert: 'frenchy!',
          title: 'french title',
        },
      });
    });

    it('maps body on all provided locales', () => {
      const bodies = PushUtils.bodiesPerLocales(
        {
          data: {
            alert: 'Yo!',
            'alert-fr': 'frenchy!',
            'alert-en': 'english',
            'title-fr': 'french title',
          },
        },
        ['fr', 'en']
      );
      expect(bodies).toEqual({
        fr: {
          data: {
            alert: 'frenchy!',
            title: 'french title',
          },
        },
        en: {
          data: {
            alert: 'english',
          },
        },
        default: {
          data: {
            alert: 'Yo!',
          },
        },
      });
    });

    it('should properly handle default cases', () => {
      expect(PushUtils.transformPushBodyForLocale({})).toEqual({});
      expect(PushUtils.stripLocalesFromBody({})).toEqual({});
      expect(PushUtils.bodiesPerLocales({ where: {} })).toEqual({
        default: { where: {} },
      });
      expect(PushUtils.groupByLocaleIdentifier([])).toEqual({ default: [] });
    });
  });

  describe('pushStatus', () => {
    it('should remove invalid installations', done => {
      const config = Config.get('test');
      const handler = pushStatusHandler(config);
      const spy = spyOn(config.database, 'update').and.callFake(() => {
        return Promise.resolve({});
      });
      const toAwait = handler.trackSent(
        [
          {
            transmitted: false,
            device: {
              deviceToken: 1,
              deviceType: 'ios',
            },
            response: { error: 'Unregistered' },
          },
          {
            transmitted: true,
            device: {
              deviceToken: 10,
              deviceType: 'ios',
            },
          },
          {
            transmitted: false,
            device: {
              deviceToken: 2,
              deviceType: 'ios',
            },
            response: { error: 'NotRegistered' },
          },
          {
            transmitted: false,
            device: {
              deviceToken: 3,
              deviceType: 'ios',
            },
            response: { error: 'InvalidRegistration' },
          },
          {
            transmitted: true,
            device: {
              deviceToken: 11,
              deviceType: 'ios',
            },
          },
          {
            transmitted: false,
            device: {
              deviceToken: 4,
              deviceType: 'ios',
            },
            response: { error: 'InvalidRegistration' },
          },
          {
            transmitted: false,
            device: {
              deviceToken: 5,
              deviceType: 'ios',
            },
            response: { error: 'InvalidRegistration' },
          },
          {
            // should not be deleted
            transmitted: false,
            device: {
              deviceToken: Parse.Error.OBJECT_NOT_FOUND,
              deviceType: 'ios',
            },
            response: { error: 'invalid error...' },
          },
        ],
        undefined,
        true
      );
      expect(spy).toHaveBeenCalled();
      expect(spy.calls.count()).toBe(1);
      const lastCall = spy.calls.mostRecent();
      expect(lastCall.args[0]).toBe('_Installation');
      expect(lastCall.args[1]).toEqual({
        deviceToken: { $in: [1, 2, 3, 4, 5] },
      });
      expect(lastCall.args[2]).toEqual({
        deviceToken: { __op: 'Delete' },
      });
      toAwait.then(done).catch(done);
    });

    it_id('764d28ab-241b-4b96-8ce9-e03541850e3f')(it)('tracks push status per UTC offsets', done => {
      const config = Config.get('test');
      const handler = pushStatusHandler(config);
      const spy = spyOn(rest, 'update').and.callThrough();
      const UTCOffset = 1;
      handler
        .setInitial()
        .then(() => {
          return handler.trackSent(
            [
              {
                transmitted: false,
                device: {
                  deviceToken: 1,
                  deviceType: 'ios',
                },
              },
              {
                transmitted: true,
                device: {
                  deviceToken: 1,
                  deviceType: 'ios',
                },
              },
            ],
            UTCOffset
          );
        })
        .then(() => {
          expect(spy).toHaveBeenCalled();
          const lastCall = spy.calls.mostRecent();
          expect(lastCall.args[2]).toBe(`_PushStatus`);
          expect(lastCall.args[4]).toEqual({
            numSent: { __op: 'Increment', amount: 1 },
            numFailed: { __op: 'Increment', amount: 1 },
            'sentPerType.ios': { __op: 'Increment', amount: 1 },
            'failedPerType.ios': { __op: 'Increment', amount: 1 },
            [`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
            [`failedPerUTCOffset.${UTCOffset}`]: {
              __op: 'Increment',
              amount: 1,
            },
            count: { __op: 'Increment', amount: -1 },
            status: 'running',
          });
          const query = new Parse.Query('_PushStatus');
          return query.get(handler.objectId, { useMasterKey: true });
        })
        .then(pushStatus => {
          const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
          expect(sentPerUTCOffset['1']).toBe(1);
          const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
          expect(failedPerUTCOffset['1']).toBe(1);
          return handler.trackSent(
            [
              {
                transmitted: false,
                device: {
                  deviceToken: 1,
                  deviceType: 'ios',
                },
              },
              {
                transmitted: true,
                device: {
                  deviceToken: 1,
                  deviceType: 'ios',
                },
              },
              {
                transmitted: true,
                device: {
                  deviceToken: 1,
                  deviceType: 'ios',
                },
              },
            ],
            UTCOffset
          );
        })
        .then(() => {
          const query = new Parse.Query('_PushStatus');
          return query.get(handler.objectId, { useMasterKey: true });
        })
        .then(pushStatus => {
          const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
          expect(sentPerUTCOffset['1']).toBe(3);
          const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
          expect(failedPerUTCOffset['1']).toBe(2);
        })
        .then(done)
        .catch(done.fail);
    });

    it('tracks push status per UTC offsets with negative offsets', done => {
      const config = Config.get('test');
      const handler = pushStatusHandler(config);
      const spy = spyOn(rest, 'update').and.callThrough();
      const UTCOffset = -6;
      handler
        .setInitial()
        .then(() => {
          return handler.trackSent(
            [
              {
                transmitted: false,
                device: {
                  deviceToken: 1,
                  deviceType: 'ios',
                },
                response: { error: 'Unregistered' },
              },
              {
                transmitted: true,
                device: {
                  deviceToken: 1,
                  deviceType: 'ios',
                },
                response: { error: 'Unregistered' },
              },
            ],
            UTCOffset
          );
        })
        .then(() => {
          expect(spy).toHaveBeenCalled();
          const lastCall = spy.calls.mostRecent();
          expect(lastCall.args[2]).toBe('_PushStatus');
          expect(lastCall.args[4]).toEqual({
            numSent: { __op: 'Increment', amount: 1 },
            numFailed: { __op: 'Increment', amount: 1 },
            'sentPerType.ios': { __op: 'Increment', amount: 1 },
            'failedPerType.ios': { __op: 'Increment', amount: 1 },
            [`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
            [`failedPerUTCOffset.${UTCOffset}`]: {
              __op: 'Increment',
              amount: 1,
            },
            count: { __op: 'Increment', amount: -1 },
            status: 'running',
          });
          done();
        });
    });
  });
});
