/* globals badger:false */

(function () {

const DNT_COMPLIANT_DOMAIN = 'eff.org',
  DNT_DOMAINS = [
    DNT_COMPLIANT_DOMAIN,
    'dnt2.example',
    'dnt3.example',
    'dnt4.example',
    'dnt5.example',
  ],
  POLICY_URL = chrome.runtime.getURL('data/dnt-policy.txt');

let utils = require('utils'),
  constants = require('constants'),
  migrations = require('migrations').Migrations,
  mdfp = require('multiDomainFP');

let clock,
  server,
  xhrSpy,
  dnt_policy_txt;

function setupBadgerStorage(badger) {
  // add foo.com, allowed as seen tracking on only one site
  badger.storage.action_map.setItem('foo.com', {
    dnt: false,
    heuristicAction: constants.ALLOW,
    nextUpdateTime: 100,
    userAction: ""
  });
  badger.storage.snitch_map.setItem('foo.com', ['a.co']);

  // add sub.bar.com,
  // blocked after having been recorded tracking on three sites
  badger.storage.action_map.setItem('bar.com', {
    dnt: false,
    heuristicAction: constants.BLOCK,
    nextUpdateTime: 100,
    userAction: ""
  });
  badger.storage.action_map.setItem('sub.bar.com', {
    dnt: false,
    heuristicAction: constants.BLOCK,
    nextUpdateTime: 100,
    userAction: ""
  });
  badger.storage.snitch_map.setItem('bar.com', ['a.co', 'b.co', 'c.co']);
}

QUnit.module("Background", {
  before: (assert) => {
    let done = assert.async();

    // fetch locally stored DNT policy
    utils.xhrRequest(POLICY_URL, function (err, data) {
      dnt_policy_txt = data;

      // set up fake server to simulate XMLHttpRequests
      server = sinon.fakeServer.create({
        respondImmediately: true
      });
      DNT_DOMAINS.forEach(domain => {
        server.respondWith(
          "GET",
          "https://" + domain + "/.well-known/dnt-policy.txt",
          [200, {}, dnt_policy_txt]
        );
      });

      // set up fake timers to simulate window.setTimeout and co.
      clock = sinon.useFakeTimers(+new Date());

      done();
    });
  },

  beforeEach: (/*assert*/) => {
    // spy on utils.xhrRequest
    xhrSpy = sinon.spy(utils, "xhrRequest");
  },

  afterEach: (/*assert*/) => {
    // reset call counts, etc. after each test
    utils.xhrRequest.restore();
  },

  after: (/*assert*/) => {
    clock.restore();
    server.restore();
  }
});

QUnit.test("DNT policy checking", (assert) => {
  const NUM_TESTS = 2,
    done = assert.async(NUM_TESTS);

  assert.expect(NUM_TESTS);

  badger.checkForDNTPolicy(DNT_COMPLIANT_DOMAIN, function (successStatus) {
    assert.ok(successStatus, "Domain returns good DNT policy");
    done();
  });

  badger.checkForDNTPolicy('ecorp.example', function (successStatus) {
    assert.notOk(successStatus, "Domain returns 200 but no valid policy");
    done();
  });

  // advance the clock enough to trigger all rate-limited calls
  clock.tick(constants.DNT_POLICY_CHECK_INTERVAL * NUM_TESTS);
});

QUnit.test("Several checks for same domain resolve to one XHR", (assert) => {
  const NUM_CHECKS = 5;

  // set recheck time to now
  badger.storage.touchDNTRecheckTime(DNT_COMPLIANT_DOMAIN, +new Date());

  for (let i = 0; i < NUM_CHECKS; i++) {
    badger.checkForDNTPolicy(DNT_COMPLIANT_DOMAIN);
  }

  // advance the clock
  clock.tick(constants.DNT_POLICY_CHECK_INTERVAL * NUM_CHECKS);

  assert.equal(xhrSpy.callCount, 1, "XHR method gets called exactly once");
  assert.equal(
    xhrSpy.getCall(0).args[0],
    "https://" + DNT_COMPLIANT_DOMAIN + "/.well-known/dnt-policy.txt",
    "XHR method gets called with expected DNT URL"
  );
});

QUnit.test("DNT checking is rate limited", (assert) => {
  const NUM_TESTS = DNT_DOMAINS.length;

  let done = assert.async(NUM_TESTS);

  assert.expect(NUM_TESTS);

  for (let i = 0; i < NUM_TESTS; i++) {
    badger.checkForDNTPolicy(
      DNT_DOMAINS[i],
      function () { // eslint-disable-line no-loop-func
        assert.equal(xhrSpy.callCount, i+1);
        clock.tick(constants.DNT_POLICY_CHECK_INTERVAL);
        done();
      }
    );
  }
});

QUnit.test("DNT checking obeys user setting", (assert) => {
  const NUM_TESTS = DNT_DOMAINS.length;

  let done = assert.async(NUM_TESTS);
  let old_dnt_check_func = badger.isCheckingDNTPolicyEnabled;

  assert.expect(NUM_TESTS);
  badger.isCheckingDNTPolicyEnabled = () => false;

  for (let i = 0; i < NUM_TESTS; i++) {
    badger.checkForDNTPolicy(DNT_DOMAINS[i]);
    clock.tick(constants.DNT_POLICY_CHECK_INTERVAL);
    assert.equal(xhrSpy.callCount, 0);
    done();
  }

  badger.isCheckingDNTPolicyEnabled = old_dnt_check_func;
});

// test #1972
QUnit.test("mergeUserData does not unblock formerly blocked domains", (assert) => {
  setupBadgerStorage(badger);

  const SITE_DOMAINS = ['a.co', 'b.co', 'c.co'],
    USER_DATA = {
      action_map: {
        'foo.com': {
          dnt: false,
          heuristicAction: constants.BLOCK,
          nextUpdateTime: 100,
          userAction: ""
        }
      },
      snitch_map: {
        'foo.com': SITE_DOMAINS
      },
      settings_map: {
        migrationLevel: 0
      }
    };

  badger.mergeUserData(USER_DATA);

  assert.equal(
    badger.storage.action_map.getItem('foo.com').heuristicAction,
    constants.BLOCK,
    "foo.com was blocked"
  );
  assert.deepEqual(
    badger.storage.snitch_map.getItem('foo.com'),
    SITE_DOMAINS,
    "snitch map was migrated"
  );

  badger.runMigrations();

  assert.equal(
    badger.storage.action_map.getItem('foo.com').heuristicAction,
    constants.BLOCK,
    "foo.com is still blocked after running migrations"
  );
});

QUnit.test("user-blocked domains keep their tracking history", (assert) => {
  const SITE_DOMAINS = ['a.co', 'b.co'],
    USER_DATA = {
      action_map: {
        'foo.com': {
          dnt: false,
          heuristicAction: constants.ALLOW,
          nextUpdateTime: 100,
          userAction: constants.USER_BLOCK
        }
      },
      snitch_map: {
        'foo.com': SITE_DOMAINS
      }
    };

  badger.mergeUserData(USER_DATA);

  assert.equal(
    badger.storage.getAction('foo.com'),
    constants.USER_BLOCK,
    "foo.com was blocked"
  );
  assert.deepEqual(
    badger.storage.snitch_map.getItem('foo.com'),
    SITE_DOMAINS,
    "snitch map was migrated"
  );
});

QUnit.test("merging snitch maps results in a blocked domain", (assert) => {
  setupBadgerStorage(badger);

  // https://github.com/EFForg/privacybadger/pull/2082#issuecomment-401942070
  const USER_DATA = {
    action_map: {
      'foo.com': {
        dnt: false,
        heuristicAction: constants.ALLOW,
        nextUpdateTime: 100,
        userAction: ""
      }
    },
    snitch_map: {'foo.com': ['b.co', 'c.co']},
  };

  badger.mergeUserData(USER_DATA);

  assert.equal(
    badger.storage.action_map.getItem('foo.com').heuristicAction,
    constants.BLOCK,
    "foo.com was blocked"
  );
  assert.deepEqual(
    badger.storage.snitch_map.getItem('foo.com'),
    ['a.co', 'b.co', 'c.co'],
    "snitch map was combined"
  );
});

QUnit.test("subdomain that is not blocked does not override subdomain that is", (assert) => {
  setupBadgerStorage(badger);

  const USER_DATA = {
    action_map: {
      'sub.bar.com': {
        dnt: false,
        heuristicAction: constants.ALLOW,
        nextUpdateTime: 100,
        userAction: ""
      }
    },
    snitch_map: {'bar.com': ['a.co']}
  };

  badger.mergeUserData(USER_DATA);

  assert.equal(
    badger.storage.action_map.getItem('sub.bar.com').heuristicAction,
    constants.BLOCK,
    "sub.bar.com is still blocked"
  );
  assert.deepEqual(
    badger.storage.snitch_map.getItem('bar.com'),
    ['a.co', 'b.co', 'c.co'],
    "snitch map was preserved"
  );
});

QUnit.test("subdomains on the yellowlist are preserved", (assert) => {
  const DOMAIN = "example.com",
    SUBDOMAIN = "cdn.example.com",
    USER_DATA = {
      action_map: {
        [DOMAIN]: {
          dnt: false,
          heuristicAction: constants.BLOCK,
          nextUpdateTime: 100,
          userAction: ''
        },
        [SUBDOMAIN]: {
          dnt: false,
          heuristicAction: constants.ALLOW,
          nextUpdateTime: 0,
          userAction: ''
        }
      },
      snitch_map: {
        [DOMAIN]: ['a.co', 'b.co', 'c.co'],
      }
    };

  const actionMap = badger.storage.getStore('action_map'),
    snitchMap = badger.storage.getStore('snitch_map');

  // merge in a blocked parent domain and a subdomain
  badger.mergeUserData(USER_DATA);

  assert.notOk(actionMap.getItem(SUBDOMAIN),
    SUBDOMAIN + " should have been discarded during merge"
  );

  // clean up
  actionMap.deleteItem(DOMAIN);
  actionMap.deleteItem(SUBDOMAIN);
  snitchMap.deleteItem(DOMAIN);

  // now add subdomain to yellowlist
  badger.storage.getStore('cookieblock_list')
    .setItem(SUBDOMAIN, true);

  // and do the merge again
  badger.mergeUserData(USER_DATA);

  assert.ok(actionMap.getItem(SUBDOMAIN),
    SUBDOMAIN + " should be present in action_map"
  );
  assert.equal(
    actionMap.getItem(SUBDOMAIN).heuristicAction,
    constants.COOKIEBLOCK,
    SUBDOMAIN + " should be cookieblocked"
  );
});

QUnit.test("forgetFirstPartySnitches migration properly handles snitch entries with no MDFP entries", (assert) => {
  const actionMap = badger.storage.getStore('action_map'),
    snitchMap = badger.storage.getStore('snitch_map');

  let snitchNoMDFP = {
    'amazon.com': ['amazonads.com', 'amazing.com', 'amazonrainforest.com']
  };

  let actionNoMDFP = {
    'amazon.com': {
      heuristicAction: "cookieblock",
      userAction: "",
      dnt: false,
      nextUpdateTime: 0,
    }
  };

  snitchMap.updateObject(snitchNoMDFP);
  actionMap.updateObject(actionNoMDFP);
  migrations.forgetFirstPartySnitches(badger);

  assert.deepEqual(
    actionMap.getItem('amazon.com'),
    actionNoMDFP['amazon.com'],
    "action map preserved for domain with no MDFP snitch entries"
  );

  assert.deepEqual(
    snitchMap.getItem('amazon.com'),
    snitchNoMDFP['amazon.com'],
    "snitch map entry with no MDFP domains remains the same after migration runs"
  );
});

QUnit.test("forgetFirstPartySnitches migration properly handles snitch entries with some MDFP entries", (assert) => {
  const actionMap = badger.storage.getStore('action_map'),
    snitchMap = badger.storage.getStore('snitch_map');

  let snitchSomeMDFP = {
    'amazon.com': ['amazon.ca', 'amazon.co.jp', 'amazing.com']
  };

  let actionSomeMDFP = {
    'amazon.com': {
      heuristicAction: "cookieblock",
      userAction: "",
      dnt: false,
      nextUpdateTime: 0,
    }
  };

  snitchMap.updateObject(snitchSomeMDFP);
  actionMap.updateObject(actionSomeMDFP);
  migrations.forgetFirstPartySnitches(badger);

  assert.equal(
    badger.storage.getAction('amazon.com'),
    constants.ALLOW,
    "Action downgraded for partial MDFP domain"
  );

  assert.deepEqual(snitchMap.getItem('amazon.com'),
    ["amazing.com"],
    'forget first party migration properly removes MDFP domains and leaves regular domains');
});

QUnit.test("forgetFirstPartySnitches migration properly handles snitch entries with all MDFP entries", (assert) => {
  const actionMap = badger.storage.getStore('action_map'),
    snitchMap = badger.storage.getStore('snitch_map');

  let snitchAllMDFP = {
    'amazon.com': ['amazon.ca', 'amazon.co.jp', 'amazon.es']
  };

  let actionAllMDFP = {
    'amazon.com': {
      heuristicAction: "cookieblock",
      userAction: "",
      dnt: false,
      nextUpdateTime: 0,
    }
  };

  // confirm all entries are MDFP
  snitchAllMDFP["amazon.com"].forEach((domain) => {
    assert.ok(
      mdfp.isMultiDomainFirstParty('amazon.com', domain),
      domain + " is indeed MDFP to amazon.com"
    );
  });

  snitchMap.updateObject(snitchAllMDFP);
  actionMap.updateObject(actionAllMDFP);
  migrations.forgetFirstPartySnitches(badger);

  assert.notOk(snitchMap.getItem('amazon.com'),
    'forget first party migration properly removes a snitch map entry with all MDFP domains attributed to it');

  assert.equal(
    badger.storage.getAction('amazon.com'),
    constants.NO_TRACKING,
    "Action downgraded for all MDFP domain"
  );
});

}());
