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

"use strict";

add_setup(async function () {
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("cookiebanners.service.mode");
    Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
    if (
      Services.prefs.getIntPref("cookiebanners.service.mode") !=
        Ci.nsICookieBannerService.MODE_DISABLED ||
      Services.prefs.getIntPref("cookiebanners.service.mode.privateBrowsing") !=
        Ci.nsICookieBannerService.MODE_DISABLED
    ) {
      // Restore original rules.
      Services.cookieBanners.resetRules(true);
    }
  });
});

add_task(async function test_insertAndGetRule() {
  info("Enabling cookie banner service with MODE_REJECT");
  await SpecialPowers.pushPrefEnv({
    set: [
      ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
    ],
  });

  info("Clear any preexisting rules");
  Services.cookieBanners.resetRules(false);

  is(
    Services.cookieBanners.rules.length,
    0,
    "Cookie banner service has no rules initially."
  );

  info("Test that we can't import rules with empty domain field.");
  let ruleInvalid = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  Assert.throws(
    () => {
      Services.cookieBanners.insertRule(ruleInvalid);
    },
    /NS_ERROR_FAILURE/,
    "Inserting an invalid rule missing a domain should throw."
  );

  let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  rule.domains = ["example.com"];

  Services.cookieBanners.insertRule(rule);

  is(
    rule.cookiesOptOut.length,
    0,
    "Should not have any opt-out cookies initially"
  );
  is(
    rule.cookiesOptIn.length,
    0,
    "Should not have any opt-in cookies initially"
  );

  info("Clearing preexisting cookies rules for example.com.");
  rule.clearCookies();

  info("Adding cookies to the rule for example.com.");
  rule.addCookie(
    true,
    "foo",
    "bar",
    "example.com",
    "/",
    3600,
    "",
    false,
    false,
    false,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );
  rule.addCookie(
    true,
    "foobar",
    "barfoo",
    "example.com",
    "/",
    3600,
    "",
    false,
    false,
    false,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );
  rule.addCookie(
    false,
    "foo",
    "bar",
    "foo.example.com",
    "/myPath",
    3600,
    "",
    false,
    false,
    true,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );

  info("Adding a click rule to the rule for example.com.");
  rule.addClickRule(
    "div#presence",
    false,
    Ci.nsIClickRule.RUN_TOP,
    "div#hide",
    "div#optOut",
    "div#optIn"
  );

  is(rule.cookiesOptOut.length, 2, "Should have two opt-out cookies.");
  is(rule.cookiesOptIn.length, 1, "Should have one opt-in cookie.");

  is(
    Services.cookieBanners.rules.length,
    1,
    "Cookie Banner Service has one rule."
  );

  let rule2 = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  rule2.domains = ["example.org"];

  Services.cookieBanners.insertRule(rule2);
  info("Clearing preexisting cookies rules for example.org.");
  rule2.clearCookies();

  info("Adding a cookie to the rule for example.org.");
  rule2.addCookie(
    false,
    "foo2",
    "bar2",
    "example.org",
    "/",
    0,
    "",
    false,
    false,
    false,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );

  info("Adding a click rule to the rule for example.org.");
  rule2.addClickRule(
    "div#presence",
    false,
    Ci.nsIClickRule.RUN_TOP,
    null,
    null,
    "div#optIn"
  );

  is(
    Services.cookieBanners.rules.length,
    2,
    "Cookie Banner Service has two rules."
  );

  info("Getting cookies by URI for example.com.");
  let ruleArray = Services.cookieBanners.getCookiesForURI(
    Services.io.newURI("http://example.com"),
    false
  );
  ok(
    ruleArray && Array.isArray(ruleArray),
    "getCookiesForURI should return a rule array."
  );
  is(ruleArray.length, 2, "rule array should contain 2 rules.");
  ruleArray.every(rule => {
    ok(rule instanceof Ci.nsICookieRule, "Rule should have correct type.");
    is(rule.cookie.host, "example.com", "Rule should have correct host.");
  });

  info("Clearing cookies of rule.");
  rule.clearCookies();
  is(rule.cookiesOptOut.length, 0, "Should have no opt-out cookies.");
  is(rule.cookiesOptIn.length, 0, "Should have no opt-in cookies.");

  info("Getting the click rule for example.com.");
  let clickRules = Services.cookieBanners.getClickRulesForDomain(
    "example.com",
    true
  );
  is(
    clickRules.length,
    1,
    "There should be one domain-specific click rule for example.com"
  );
  let [clickRule] = clickRules;

  is(
    clickRule.presence,
    "div#presence",
    "Should have the correct presence selector."
  );
  is(clickRule.hide, "div#hide", "Should have the correct hide selector.");
  is(
    clickRule.optOut,
    "div#optOut",
    "Should have the correct optOut selector."
  );
  is(clickRule.optIn, "div#optIn", "Should have the correct optIn selector.");

  info("Getting cookies by URI for example.org.");
  let ruleArray2 = Services.cookieBanners.getCookiesForURI(
    Services.io.newURI("http://example.org"),
    false
  );
  ok(
    ruleArray2 && Array.isArray(ruleArray2),
    "getCookiesForURI should return a rule array."
  );
  is(
    ruleArray2.length,
    0,
    "rule array should contain no rules in MODE_REJECT (opt-out only)"
  );

  info("Getting the click rule for example.org.");
  let clickRules2 = Services.cookieBanners.getClickRulesForDomain(
    "example.org",
    true
  );
  is(
    clickRules2.length,
    1,
    "There should be one domain-specific click rule for example.org"
  );
  let [clickRule2] = clickRules2;
  is(
    clickRule2.presence,
    "div#presence",
    "Should have the correct presence selector."
  );
  ok(!clickRule2.hide, "Should have no hide selector.");
  ok(!clickRule2.optOut, "Should have no target selector.");
  is(clickRule.optIn, "div#optIn", "Should have the correct optIn selector.");

  info("Switching cookiebanners.service.mode to MODE_REJECT_OR_ACCEPT.");
  await SpecialPowers.pushPrefEnv({
    set: [
      [
        "cookiebanners.service.mode",
        Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
      ],
    ],
  });

  ruleArray2 = Services.cookieBanners.getCookiesForURI(
    Services.io.newURI("http://example.org"),
    false
  );
  ok(
    ruleArray2 && Array.isArray(ruleArray2),
    "getCookiesForURI should return a rule array."
  );
  is(
    ruleArray2.length,
    1,
    "rule array should contain one rule in mode MODE_REJECT_OR_ACCEPT (opt-out or opt-in)"
  );

  info("Calling getCookiesForURI for unknown domain.");
  let ruleArrayUnknown = Services.cookieBanners.getCookiesForURI(
    Services.io.newURI("http://example.net"),
    false
  );
  ok(
    ruleArrayUnknown && Array.isArray(ruleArrayUnknown),
    "getCookiesForURI should return a rule array."
  );
  is(ruleArrayUnknown.length, 0, "rule array should contain no rules.");

  // Cleanup.
  Services.cookieBanners.resetRules(false);
});

add_task(async function test_removeRule() {
  info("Enabling cookie banner service with MODE_REJECT");
  await SpecialPowers.pushPrefEnv({
    set: [
      ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
    ],
  });

  info("Clear any preexisting rules");
  Services.cookieBanners.resetRules(false);

  is(
    Services.cookieBanners.rules.length,
    0,
    "Cookie banner service has no rules initially."
  );

  let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  rule.id = genUUID();
  rule.domains = ["example.com"];

  Services.cookieBanners.insertRule(rule);

  let rule2 = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  rule2.id = genUUID();
  rule2.domains = ["example.org"];

  Services.cookieBanners.insertRule(rule2);

  is(
    Services.cookieBanners.rules.length,
    2,
    "Cookie banner service two rules after insert."
  );

  info("Removing rule for non existent example.net");
  let ruleExampleNet = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  ruleExampleNet.id = genUUID();
  ruleExampleNet.domains = ["example.net"];
  Services.cookieBanners.removeRule(ruleExampleNet);

  is(
    Services.cookieBanners.rules.length,
    2,
    "Cookie banner service still has two rules."
  );

  info("Removing rule for non existent global rule.");
  let ruleGlobal = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  ruleGlobal.id = genUUID();
  ruleGlobal.domains = [];
  Services.cookieBanners.removeRule(ruleGlobal);

  is(
    Services.cookieBanners.rules.length,
    2,
    "Cookie banner service still has two rules."
  );

  info("Removing rule for example.com");
  Services.cookieBanners.removeRule(rule);

  is(
    Services.cookieBanners.rules.length,
    1,
    "Cookie banner service should have one rule left after remove."
  );

  is(
    Services.cookieBanners.rules[0].domains[0],
    "example.org",
    "It should be the example.org rule."
  );

  // Cleanup.
  Services.cookieBanners.resetRules(false);
});

add_task(async function test_overwriteRule() {
  info("Enabling cookie banner service with MODE_REJECT");
  await SpecialPowers.pushPrefEnv({
    set: [
      ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
    ],
  });

  info("Clear any preexisting rules");
  Services.cookieBanners.resetRules(false);

  is(
    Services.cookieBanners.rules.length,
    0,
    "Cookie banner service has no rules initially."
  );

  let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  rule.domains = ["example.com"];

  info("Adding a cookie so we can detect if the rule updates.");
  rule.addCookie(
    true,
    "foo",
    "original",
    "example.com",
    "/",
    3600,
    "",
    false,
    false,
    false,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );

  info("Adding a click rule so we can detect if the rule updates.");
  rule.addClickRule("div#original");

  Services.cookieBanners.insertRule(rule);

  let { cookie } = Services.cookieBanners.rules[0].cookiesOptOut[0];

  is(cookie.name, "foo", "Should have set the correct cookie name.");
  is(cookie.value, "original", "Should have set the correct cookie value.");

  info("Add a new rule with the same domain. It should be overwritten.");

  let ruleNew = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  ruleNew.domains = ["example.com"];

  ruleNew.addCookie(
    true,
    "foo",
    "new",
    "example.com",
    "/",
    3600,
    "",
    false,
    false,
    false,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );

  ruleNew.addClickRule("div#new");

  Services.cookieBanners.insertRule(ruleNew);

  let { cookie: cookieNew } = Services.cookieBanners.rules[0].cookiesOptOut[0];
  is(cookieNew.name, "foo", "Should have set the original cookie name.");
  is(cookieNew.value, "new", "Should have set the updated cookie value.");

  let { presence: presenceNew } = Services.cookieBanners.rules[0].clickRule;
  is(presenceNew, "div#new", "Should have set the updated presence value");

  // Cleanup.
  Services.cookieBanners.resetRules(false);
});

add_task(async function test_globalRules() {
  info("Enabling cookie banner service with MODE_REJECT");
  await SpecialPowers.pushPrefEnv({
    set: [
      ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
      ["cookiebanners.service.enableGlobalRules", true],
    ],
  });

  info("Clear any preexisting rules");
  Services.cookieBanners.resetRules(false);

  is(
    Services.cookieBanners.rules.length,
    0,
    "Cookie banner service has no rules initially."
  );

  info("Insert a site-specific rule for example.com");
  let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  rule.id = genUUID();
  rule.domains = ["example.com"];
  rule.addCookie(
    true,
    "foo",
    "new",
    "example.com",
    "/",
    3600,
    "",
    false,
    false,
    false,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );
  rule.addClickRule(
    "#cookieBannerExample",
    false,
    Ci.nsIClickRule.RUN_TOP,
    "#btnOptOut",
    "#btnOptIn"
  );
  Services.cookieBanners.insertRule(rule);

  info(
    "Insert a global rule with a cookie and a click rule. The cookie rule shouldn't be used."
  );
  let ruleGlobalA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  ruleGlobalA.id = genUUID();
  ruleGlobalA.domains = [];
  ruleGlobalA.addCookie(
    true,
    "foo",
    "new",
    "example.net",
    "/",
    3600,
    "",
    false,
    false,
    false,
    Ci.nsICookie.SAMESITE_UNSET,
    0
  );
  ruleGlobalA.addClickRule(
    "#globalCookieBanner",
    false,
    Ci.nsIClickRule.RUN_TOP,
    "#btnOptOut",
    "#btnOptIn"
  );
  Services.cookieBanners.insertRule(ruleGlobalA);

  info("Insert a second global rule");
  let ruleGlobalB = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
    Ci.nsICookieBannerRule
  );
  ruleGlobalB.id = genUUID();
  ruleGlobalB.domains = [];
  ruleGlobalB.addClickRule(
    "#globalCookieBannerB",
    false,
    Ci.nsIClickRule.RUN_TOP,
    "#btnOptOutB",
    "#btnOptIn"
  );
  Services.cookieBanners.insertRule(ruleGlobalB);

  is(
    Services.cookieBanners.rules.length,
    3,
    "Cookie Banner Service has three rules."
  );

  is(
    Services.cookieBanners.getCookiesForURI(
      Services.io.newURI("http://example.com"),
      false
    ).length,
    1,
    "There should be a cookie rule for example.com"
  );

  is(
    Services.cookieBanners.getClickRulesForDomain("example.com", true).length,
    1,
    "There should be a a click rule for example.com"
  );

  is(
    Services.cookieBanners.getCookiesForURI(
      Services.io.newURI("http://thishasnorule.com"),
      false
    ).length,
    0,
    "There should be no cookie rule for thishasnorule.com"
  );

  let clickRules = Services.cookieBanners.getClickRulesForDomain(
    "thishasnorule.com",
    true
  );
  is(
    clickRules.length,
    2,
    "There should be two click rules for thishasnorule.com"
  );
  ok(
    clickRules.every(rule => rule.presence.startsWith("#globalCookieBanner")),
    "The returned click rules should be global rules."
  );

  info("Disabling global rules");
  await SpecialPowers.pushPrefEnv({
    set: [["cookiebanners.service.enableGlobalRules", false]],
  });

  is(
    Services.cookieBanners.rules.length,
    1,
    "Cookie Banner Service has 1 rule."
  );

  is(
    Services.cookieBanners.rules[0].id,
    rule.id,
    "It should be the domain specific rule"
  );

  is(
    Services.cookieBanners.getCookiesForURI(
      Services.io.newURI("http://thishasnorule.com"),
      false
    ).length,
    0,
    "There should be no cookie rule for thishasnorule.com"
  );

  is(
    Services.cookieBanners.getClickRulesForDomain("thishasnorule.com", true)
      .length,
    0,
    "There should be no click rules for thishasnorule.com since global rules are disabled"
  );
});
