/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

'use strict';

const {PushDB, PushService, PushServiceWebSocket} = serviceExports;

const userAgentID = '28cd09e2-7506-42d8-9e50-b02785adc7ef';

var db;

function run_test() {
  do_get_profile();
  setPrefs({
    userAgentID,
  });
  run_next_test();
}

let putRecord = async function(perm, record) {
  let uri = Services.io.newURI(record.scope);

  Services.perms.add(uri, 'desktop-notification',
    Ci.nsIPermissionManager[perm]);
  do_register_cleanup(() => {
    Services.perms.remove(uri, 'desktop-notification');
  });

  await db.put(record);
};

add_task(async function test_expiration_history_observer() {
  db = PushServiceWebSocket.newPushDB();
  do_register_cleanup(() => db.drop().then(_ => db.close()));

  // A registration that we'll expire...
  await putRecord('ALLOW_ACTION', {
    channelID: '379c0668-8323-44d2-a315-4ee83f1a9ee9',
    pushEndpoint: 'https://example.org/push/1',
    scope: 'https://example.com/deals',
    pushCount: 0,
    lastPush: 0,
    version: null,
    originAttributes: '',
    quota: 16,
  });

  // ...And a registration that we'll evict on startup.
  await putRecord('ALLOW_ACTION', {
    channelID: '4cb6e454-37cf-41c4-a013-4e3a7fdd0bf1',
    pushEndpoint: 'https://example.org/push/3',
    scope: 'https://example.com/stuff',
    pushCount: 0,
    lastPush: 0,
    version: null,
    originAttributes: '',
    quota: 0,
  });

  await PlacesTestUtils.addVisits({
    uri: 'https://example.com/infrequent',
    title: 'Infrequently-visited page',
    visitDate: (Date.now() - 14 * 24 * 60 * 60 * 1000) * 1000,
    transition: Ci.nsINavHistoryService.TRANSITION_LINK
  });

  let unregisterDone;
  let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
  let subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) =>
    data == 'https://example.com/stuff');

  PushService.init({
    serverURI: 'wss://push.example.org/',
    db,
    makeWebSocket(uri) {
      return new MockWebSocket(uri, {
        onHello(request) {
          this.serverSendMsg(JSON.stringify({
            messageType: 'hello',
            status: 200,
            uaid: userAgentID,
          }));
          this.serverSendMsg(JSON.stringify({
            messageType: 'notification',
            updates: [{
              channelID: '379c0668-8323-44d2-a315-4ee83f1a9ee9',
              version: 2,
            }],
          }));
        },
        onUnregister(request) {
          equal(request.channelID, '379c0668-8323-44d2-a315-4ee83f1a9ee9', 'Dropped wrong channel ID');
          equal(request.code, 201, 'Expected quota exceeded unregister reason');
          unregisterDone();
        },
        onACK(request) {},
      });
    }
  });

  await subChangePromise;
  await unregisterPromise;

  let expiredRecord = await db.getByKeyID('379c0668-8323-44d2-a315-4ee83f1a9ee9');
  strictEqual(expiredRecord.quota, 0, 'Expired record not updated');

  let notifiedScopes = [];
  subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) => {
    notifiedScopes.push(data);
    return notifiedScopes.length == 2;
  });

  // Add an expired registration that we'll revive later using the idle
  // observer.
  await putRecord('ALLOW_ACTION', {
    channelID: 'eb33fc90-c883-4267-b5cb-613969e8e349',
    pushEndpoint: 'https://example.org/push/2',
    scope: 'https://example.com/auctions',
    pushCount: 0,
    lastPush: 0,
    version: null,
    originAttributes: '',
    quota: 0,
  });
  // ...And an expired registration that we'll revive on fetch.
  await putRecord('ALLOW_ACTION', {
    channelID: '6b2d13fe-d848-4c5f-bdda-e9fc89727dca',
    pushEndpoint: 'https://example.org/push/4',
    scope: 'https://example.net/sales',
    pushCount: 0,
    lastPush: 0,
    version: null,
    originAttributes: '',
    quota: 0,
  });

  // Now visit the site...
  await PlacesTestUtils.addVisits({
    uri: 'https://example.com/another-page',
    title: 'Infrequently-visited page',
    visitDate: Date.now() * 1000,
    transition: Ci.nsINavHistoryService.TRANSITION_LINK
  });
  Services.obs.notifyObservers(null, 'idle-daily');

  // And we should receive notifications for both scopes.
  await subChangePromise;
  deepEqual(notifiedScopes.sort(), [
    'https://example.com/auctions',
    'https://example.com/deals'
  ], 'Wrong scopes for subscription changes');

  let aRecord = await db.getByKeyID('379c0668-8323-44d2-a315-4ee83f1a9ee9');
  ok(!aRecord, 'Should drop expired record');

  let bRecord = await db.getByKeyID('eb33fc90-c883-4267-b5cb-613969e8e349');
  ok(!bRecord, 'Should drop evicted record');

  // Simulate a visit to a site with an expired registration, then fetch the
  // record. This should drop the expired record and fire an observer
  // notification.
  await PlacesTestUtils.addVisits({
    uri: 'https://example.net/sales',
    title: 'Firefox plushies, 99% off',
    visitDate: Date.now() * 1000,
    transition: Ci.nsINavHistoryService.TRANSITION_LINK
  });
  subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) => {
    if (data == 'https://example.net/sales') {
      ok(subject.isCodebasePrincipal,
        'Should pass subscription principal as the subject');
      return true;
    }
  });
  let record = await PushService.registration({
    scope: 'https://example.net/sales',
    originAttributes: '',
  });
  ok(!record, 'Should not return evicted record');
  ok(!(await db.getByKeyID('6b2d13fe-d848-4c5f-bdda-e9fc89727dca')),
    'Should drop evicted record on fetch');
  await subChangePromise;
});
