/* globals badger:false */

(function () {

function get_ylist() {
  return badger.storage.getStore('cookieblock_list').getItemClones();
}

let constants = require('constants');

// fake server to simulate XMLHttpRequests
let server;

QUnit.module("Yellowlist", (hooks) => {
  hooks.before((/*assert*/) => {
    server = sinon.fakeServer.create({
      respondImmediately: true
    });
  });

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

  QUnit.test("Updating to a valid list", (assert) => {
    let done = assert.async();
    assert.expect(3);

    let ylist = get_ylist();
    assert.ok(!!Object.keys(ylist).length, "yellowlist is not empty");

    // remove a domain
    let removed_domain = Object.keys(ylist)[0];
    delete ylist[removed_domain];

    // add a domain
    const NEW_YLIST_DOMAIN = "widgets.example.com";
    ylist[NEW_YLIST_DOMAIN] = true;

    // respond with the modified list
    server.respondWith("GET", constants.YELLOWLIST_URL,
      [200, {}, Object.keys(ylist).join("\n")]);

    badger.updateYellowlist(function (err) {
      assert.notOk(err, "callback status indicates success");
      assert.deepEqual(get_ylist(), ylist, "list got updated");
      done();
    });
  });

  QUnit.test("Updating receives a blank response", (assert) => {
    let done = assert.async();
    assert.expect(3);

    let ylist = get_ylist();
    assert.ok(!!Object.keys(ylist).length, "yellowlist is not empty");

    // respond with no content
    server.respondWith("GET", constants.YELLOWLIST_URL,
      [200, {}, ""]);

    badger.updateYellowlist(function (err) {
      assert.ok(err, "callback status indicates failure");
      assert.deepEqual(get_ylist(), ylist, "list did not get updated");
      done();
    });
  });

  QUnit.test("Updating receives an invalid response", (assert) => {
    let BAD_RESPONSES = [
      "page not found",
      "page\nnot\nfound",
      "pagenotfound",
      "eff.org\n...\n",
      "...eff.org...",
      "<html><body>eff.org</body></html>",
    ];

    let done = assert.async(BAD_RESPONSES.length);
    assert.expect(1 + (2 * BAD_RESPONSES.length));

    let ylist = get_ylist();
    assert.ok(!!Object.keys(ylist).length, "yellowlist is not empty");

    BAD_RESPONSES.forEach(response => {
      // respond with stuff that may look like the yellowlist but is not
      server.respondWith("GET", constants.YELLOWLIST_URL,
        [200, {}, response]);

      badger.updateYellowlist(function (err) {
        assert.ok(err,
          "callback status indicates failure for " + JSON.stringify(response));
        assert.deepEqual(get_ylist(), ylist,
          "list did not get updated for " + JSON.stringify(response));
        done();
      });
    });
  });

  QUnit.test("Updating gets a server error", (assert) => {
    let done = assert.async();
    assert.expect(1);

    // respond with a 404 error
    server.respondWith("GET", constants.YELLOWLIST_URL,
      [404, {}, "page not found"]);

    badger.updateYellowlist(function (err) {
      assert.ok(err, "callback status indicates failure");
      done();
    });
  });

  QUnit.test("added domains get cookieblocked", (assert) => {
    const DOMAIN = "example.com";

    let done = assert.async();
    assert.expect(2);

    // mark domain for blocking
    badger.storage.setupHeuristicAction(DOMAIN, constants.BLOCK);

    // respond with this domain added
    let ylist = get_ylist();
    ylist[DOMAIN] = true;
    server.respondWith("GET", constants.YELLOWLIST_URL,
      [200, {}, Object.keys(ylist).join("\n")]);

    // update yellowlist
    badger.updateYellowlist(function (err) {
      assert.notOk(err, "callback status indicates success");

      // check that the domain got cookieblocked
      assert.equal(
        badger.storage.getAction(DOMAIN),
        constants.COOKIEBLOCK,
        "domain is marked for cookieblocking"
      );

      done();
    });
  });

  QUnit.test("Reapplying yellowlist updates", (assert) => {
    // these are all on the yellowlist
    let DOMAINS = [
      // domain, action
      ["books.google.com", null], // null means do not record
      ["clients6.google.com", ""],
      ["storage.googleapis.com", constants.BLOCK],
    ];

    // set up test data
    for (let i = 0; i < DOMAINS.length; i++) {
      let [domain, action] = DOMAINS[i];
      if (action !== null) {
        // record the domain with specified action
        badger.storage.setupHeuristicAction(domain, action);

        // block the base domain
        badger.storage.setupHeuristicAction(
          window.getBaseDomain(domain), constants.BLOCK);
      }
    }

    // (re)apply yellowlist updates
    require("migrations").Migrations.reapplyYellowlist(badger);

    // all test domains should be now set to "cookieblock"
    for (let i = 0; i < DOMAINS.length; i++) {
      let [domain,] = DOMAINS[i];
      assert.equal(
        badger.storage.getBestAction(domain),
        constants.COOKIEBLOCK,
        domain + " is cookieblocked"
      );
    }
  });

  QUnit.module("Removing domains", () => {
    let TESTS = [
      {
        name: "Basic scenario",
        domains: {
          'example.com': {
            yellowlist: true,
            remove: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.BLOCK,
            expectedBest: constants.BLOCK
          },
        }
      },

      {
        name: "Parent is on yellowlist",
        domains: {
          'widgets.example.com': {
            yellowlist: true,
            initial: constants.COOKIEBLOCK
          },
          'cdn.widgets.example.com': {
            yellowlist: true,
            remove: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.COOKIEBLOCK,
            expectedBest: constants.COOKIEBLOCK
          },
        }
      },

      // scenario from https://github.com/EFForg/privacybadger/issues/1474
      {
        name: "Parent is on yellowlist and is a PSL TLD (not in action map)",
        domains: {
          'googleapis.com': {
            yellowlist: true,
            expected: constants.NO_TRACKING,
            expectedBest: constants.NO_TRACKING,
          },
          'ajax.googleapis.com': {
            yellowlist: true,
            remove: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.COOKIEBLOCK,
            expectedBest: constants.COOKIEBLOCK
          },
        }
      },

      {
        name: "Child is on yellowlist",
        domains: {
          'widgets.example.com': {
            yellowlist: true,
            remove: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.BLOCK,
            expectedBest: constants.BLOCK
          },
          'cdn.widgets.example.com': {
            yellowlist: true,
            expected: constants.COOKIEBLOCK,
            expectedBest: constants.COOKIEBLOCK
          },
        }
      },

      {
        name: "Removing parent blocks subdomains",
        domains: {
          // parent domain is yellowlisted and cookieblocked
          'example.com': {
            yellowlist: true,
            remove: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.BLOCK,
            expectedBest: constants.BLOCK
          },
          // non-yellowlisted subdomain
          'cdn1.example.com': {
            initial: constants.COOKIEBLOCK,
            expected: constants.BLOCK,
            expectedBest: constants.BLOCK
          },
          // another non-yellowlisted subdomain
          'cdn2.example.com': {
            initial: constants.COOKIEBLOCK,
            expected: constants.BLOCK,
            expectedBest: constants.BLOCK
          },
        }
      },

      {
        name: "Parent is blocked",
        domains: {
          'example.com': {
            initial: constants.BLOCK,
          },
          // removing from yellowlist will get this blocked
          'www.example.com': {
            yellowlist: true,
            remove: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.BLOCK,
            expectedBest: constants.BLOCK
          },
          // removing from yellowlist will get this blocked
          's-static.ak.example.com': {
            yellowlist: true,
            remove: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.BLOCK,
            expectedBest: constants.BLOCK
          },
          // yellowlisted and cookieblocked, should stay the same
          'video.example.com': {
            yellowlist: true,
            initial: constants.COOKIEBLOCK,
            expected: constants.COOKIEBLOCK,
            expectedBest: constants.COOKIEBLOCK
          },
          // non-tracking, should stay the same
          'ampcid.example.com': {
            initial: "",
            expected: constants.NO_TRACKING,
            expectedBest: constants.BLOCK
          },
        }
      },

      // scenario from https://github.com/EFForg/privacybadger/issues/1474:
      // using endsWith() and removing "" blocked all domains in action map
      // that were also on the yellowlist, regardless of their status
      {
        name: "Removing blank domain does not block entire yellowlist",
        domains: {
          '': {
            yellowlist: true,
            remove: true
          },
          // on yellowlist and in action map as non-tracking
          'avatars0.example.com': {
            yellowlist: true,
            initial: "",
            expected: constants.NO_TRACKING,
            expectedBest: constants.NO_TRACKING
          },
          // on yellowlist and in action map but not yet blocked
          'api.example.net': {
            yellowlist: true,
            initial: constants.ALLOW,
            expected: constants.ALLOW,
            expectedBest: constants.ALLOW
          }
        }
      }
    ];

    QUnit.test("googleapis.com is still a PSL TLD", (assert) => {
      assert.notEqual(
        window.getBaseDomain("ajax.googleapis.com"),
        "googleapis.com",
        "PSL yellowlist test depends on googleapis.com remaining a PSL TLD"
      );
    });

    TESTS.forEach(test => {
      QUnit.test(test.name, (assert) => {

        let done = assert.async();

        // to get num. of assertions, tally the expected/expectedBest props,
        // and add one for the yellowlist update assertion
        assert.expect(1 + Object.keys(test.domains).reduce((memo, domain) => {
          let data = test.domains[domain];
          if (data.hasOwnProperty('expected')) {
            memo++;
          }
          if (data.hasOwnProperty('expectedBest')) {
            memo++;
          }
          return memo;
        }, 0));

        let ylistStorage = badger.storage.getStore('cookieblock_list');

        // set up cookieblocking
        for (let domain in test.domains) {
          let conf = test.domains[domain];
          if (conf.yellowlist) {
            ylistStorage.setItem(domain, true);
          }
          if (conf.hasOwnProperty("initial")) {
            badger.storage.setupHeuristicAction(domain, conf.initial);
          }
        }

        // update the yellowlist making sure removed domains aren't on it
        const ylist = ylistStorage.getItemClones();
        for (let domain in test.domains) {
          if (test.domains[domain].remove) {
            delete ylist[domain];
          }
        }
        server.respondWith("GET", constants.YELLOWLIST_URL,
          [200, {}, Object.keys(ylist).join("\n")]);

        badger.updateYellowlist(err => {
          assert.notOk(err, "callback status indicates success");

          for (let domain in test.domains) {
            let expected, data = test.domains[domain];

            if (data.hasOwnProperty('expected')) {
              expected = data.expected;
              assert.equal(
                badger.storage.getAction(domain),
                expected,
                `action on ${domain} should be "${expected}"`
              );
            }

            if (data.hasOwnProperty('expectedBest')) {
              expected = data.expectedBest;
              assert.equal(
                badger.storage.getBestAction(domain),
                expected,
                `best action for ${domain} should be "${expected}"`
              );
            }
          }

          done();
        });

      });
    });
  });

});

}());
