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

"use strict";

// Test for addon suggestions.

const TEST_MERINO_SUGGESTIONS = [
  {
    provider: "amo",
    icon: "https://example.com/first.svg",
    url: "https://example.com/first-addon",
    title: "First Addon",
    description: "This is a first addon",
    custom_details: {
      amo: {
        rating: "5",
        number_of_ratings: "1234567",
        guid: "first@addon",
      },
    },
    is_top_pick: true,
  },
  {
    provider: "amo",
    icon: "https://example.com/second.png",
    url: "https://example.com/second-addon",
    title: "Second Addon",
    description: "This is a second addon",
    custom_details: {
      amo: {
        rating: "4.5",
        number_of_ratings: "123",
        guid: "second@addon",
      },
    },
    is_sponsored: true,
    is_top_pick: false,
  },
  {
    provider: "amo",
    icon: "https://example.com/third.svg",
    url: "https://example.com/third-addon",
    title: "Third Addon",
    description: "This is a third addon",
    custom_details: {
      amo: {
        rating: "0",
        number_of_ratings: "0",
        guid: "third@addon",
      },
    },
    is_top_pick: false,
  },
  {
    provider: "amo",
    icon: "https://example.com/fourth.svg",
    url: "https://example.com/fourth-addon",
    title: "Fourth Addon",
    description: "This is a fourth addon",
    custom_details: {
      amo: {
        rating: "4",
        number_of_ratings: "4",
        guid: "fourth@addon",
      },
    },
  },
];

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["browser.urlbar.quicksuggest.enabled", true],
      ["browser.urlbar.quicksuggest.remoteSettings.enabled", false],
      ["browser.urlbar.bestMatch.enabled", true],
    ],
  });

  await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });

  await QuickSuggestTestUtils.ensureQuickSuggestInit({
    merinoSuggestions: TEST_MERINO_SUGGESTIONS,
  });
});

add_task(async function basic() {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.addons.featureGate", true]],
  });

  for (const merinoSuggestion of TEST_MERINO_SUGGESTIONS) {
    MerinoTestUtils.server.response.body.suggestions = [merinoSuggestion];

    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      value: "only match the Merino suggestion",
    });
    Assert.equal(UrlbarTestUtils.getResultCount(window), 2);

    const { element, result } = await UrlbarTestUtils.getDetailsOfResultAt(
      window,
      1
    );
    const row = element.row;
    const icon = row.querySelector(".urlbarView-dynamic-addons-icon");
    Assert.equal(icon.src, merinoSuggestion.icon);
    const url = row.querySelector(".urlbarView-dynamic-addons-url");
    Assert.equal(url.textContent, merinoSuggestion.url);
    const title = row.querySelector(".urlbarView-dynamic-addons-title");
    Assert.equal(title.textContent, merinoSuggestion.title);
    const description = row.querySelector(
      ".urlbarView-dynamic-addons-description"
    );
    Assert.equal(description.textContent, merinoSuggestion.description);
    const reviews = row.querySelector(".urlbarView-dynamic-addons-reviews");
    Assert.equal(
      reviews.textContent,
      `${new Intl.NumberFormat().format(
        Number(merinoSuggestion.custom_details.amo.number_of_ratings)
      )} reviews`
    );

    const isTopPick = merinoSuggestion.is_top_pick ?? true;
    if (isTopPick) {
      Assert.equal(result.suggestedIndex, 1);
    } else if (merinoSuggestion.is_sponsored) {
      Assert.equal(
        result.suggestedIndex,
        UrlbarPrefs.get("quickSuggestSponsoredIndex")
      );
    } else {
      Assert.equal(
        result.suggestedIndex,
        UrlbarPrefs.get("quickSuggestNonSponsoredIndex")
      );
    }

    const onLoad = BrowserTestUtils.browserLoaded(
      gBrowser.selectedBrowser,
      false,
      merinoSuggestion.url
    );
    EventUtils.synthesizeMouseAtCenter(row, {});
    await onLoad;
    Assert.ok(true, "Expected page is loaded");

    await PlacesUtils.history.clear();
  }

  await SpecialPowers.popPrefEnv();
});

add_task(async function ratings() {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.addons.featureGate", true]],
  });

  const testRating = [
    "0",
    "0.24",
    "0.25",
    "0.74",
    "0.75",
    "1",
    "1.24",
    "1.25",
    "1.74",
    "1.75",
    "2",
    "2.24",
    "2.25",
    "2.74",
    "2.75",
    "3",
    "3.24",
    "3.25",
    "3.74",
    "3.75",
    "4",
    "4.24",
    "4.25",
    "4.74",
    "4.75",
    "5",
  ];
  const baseMerinoSuggestion = JSON.parse(
    JSON.stringify(TEST_MERINO_SUGGESTIONS[0])
  );

  for (const rating of testRating) {
    baseMerinoSuggestion.custom_details.amo.rating = rating;
    MerinoTestUtils.server.response.body.suggestions = [baseMerinoSuggestion];

    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      value: "only match the Merino suggestion",
    });
    Assert.equal(UrlbarTestUtils.getResultCount(window), 2);

    const { element } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);

    const ratingElements = element.row.querySelectorAll(
      ".urlbarView-dynamic-addons-rating"
    );
    Assert.equal(ratingElements.length, 5);

    for (let i = 0; i < ratingElements.length; i++) {
      const ratingElement = ratingElements[i];

      const distanceToFull = Number(rating) - i;
      let fill = "full";
      if (distanceToFull < 0.25) {
        fill = "empty";
      } else if (distanceToFull < 0.75) {
        fill = "half";
      }
      Assert.equal(ratingElement.getAttribute("fill"), fill);
    }
  }

  await SpecialPowers.popPrefEnv();
});

add_task(async function disable() {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.addons.featureGate", false]],
  });

  // Restore AdmWikipedia suggestions.
  MerinoTestUtils.server.reset();
  // Add one Addon suggestion that is higher score than AdmWikipedia.
  MerinoTestUtils.server.response.body.suggestions.push(
    Object.assign({}, TEST_MERINO_SUGGESTIONS[0], { score: 2 })
  );

  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    value: "only match the Merino suggestion",
  });
  Assert.equal(UrlbarTestUtils.getResultCount(window), 2);

  const { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
  Assert.equal(result.payload.telemetryType, "adm_sponsored");

  MerinoTestUtils.server.response.body.suggestions = TEST_MERINO_SUGGESTIONS;
  await SpecialPowers.popPrefEnv();
});

add_task(async function resultMenu_showLessFrequently() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["browser.urlbar.addons.featureGate", true],
      ["browser.urlbar.addons.showLessFrequentlyCount", 0],
    ],
  });

  const cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({
    addonsShowLessFrequentlyCap: 3,
  });

  // Sanity check.
  Assert.equal(UrlbarPrefs.get("addonsShowLessFrequentlyCap"), 3);
  Assert.equal(UrlbarPrefs.get("addons.showLessFrequentlyCount"), 0);

  await doShowLessFrequently({
    input: "aaa b",
    expected: {
      isSuggestionShown: true,
      isMenuItemShown: true,
    },
  });
  Assert.equal(UrlbarPrefs.get("addons.showLessFrequentlyCount"), 1);

  await doShowLessFrequently({
    input: "aaa b",
    expected: {
      isSuggestionShown: true,
      isMenuItemShown: true,
    },
  });
  Assert.equal(UrlbarPrefs.get("addons.showLessFrequentlyCount"), 2);

  await doShowLessFrequently({
    input: "aaa b",
    expected: {
      isSuggestionShown: true,
      isMenuItemShown: true,
    },
  });
  Assert.equal(UrlbarPrefs.get("addons.showLessFrequentlyCount"), 3);

  await doShowLessFrequently({
    input: "aaa b",
    expected: {
      // The suggestion should not display since addons.showLessFrequentlyCount
      // is 3 and the substring (" b") after the first word ("aaa") is 2 chars
      // long.
      isSuggestionShown: false,
    },
  });

  await doShowLessFrequently({
    input: "aaa bb",
    expected: {
      // The suggestion should display, but item should not shown since the
      // addons.showLessFrequentlyCount reached to addonsShowLessFrequentlyCap
      // already.
      isSuggestionShown: true,
      isMenuItemShown: false,
    },
  });

  await cleanUpNimbus();
  await SpecialPowers.popPrefEnv();
});

// Tests the "Not interested" result menu dismissal command.
add_task(async function resultMenu_notInterested() {
  await doDismissTest("not_interested");
});

// Tests the "Not relevant" result menu dismissal command.
add_task(async function notRelevant() {
  await doDismissTest("not_relevant");
});

add_task(async function rowLabel() {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.addons.featureGate", true]],
  });

  const testCases = [
    {
      bestMatch: true,
      expected: "Firefox extension",
    },
    {
      bestMatch: false,
      expected: "Firefox Suggest",
    },
  ];

  for (const { bestMatch, expected } of testCases) {
    await SpecialPowers.pushPrefEnv({
      set: [["browser.urlbar.bestMatch.enabled", bestMatch]],
    });

    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      value: "only match the Merino suggestion",
    });
    Assert.equal(UrlbarTestUtils.getResultCount(window), 2);

    const { element } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
    const row = element.row;
    Assert.equal(row.getAttribute("label"), expected);

    await SpecialPowers.popPrefEnv();
  }

  await SpecialPowers.popPrefEnv();
});

add_task(async function treatmentB() {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.addons.featureGate", true]],
  });

  const cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({
    addonsUITreatment: "b",
  });
  // Sanity check.
  Assert.equal(UrlbarPrefs.get("addonsUITreatment"), "b");

  const merinoSuggestion = TEST_MERINO_SUGGESTIONS[0];
  MerinoTestUtils.server.response.body.suggestions = [merinoSuggestion];

  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    value: "only match the Merino suggestion",
  });
  Assert.equal(UrlbarTestUtils.getResultCount(window), 2);

  const { element } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
  const row = element.row;
  const icon = row.querySelector(".urlbarView-dynamic-addons-icon");
  Assert.equal(icon.src, merinoSuggestion.icon);
  const url = row.querySelector(".urlbarView-dynamic-addons-url");
  Assert.equal(url.textContent, merinoSuggestion.url);
  const title = row.querySelector(".urlbarView-dynamic-addons-title");
  Assert.equal(title.textContent, merinoSuggestion.title);
  const description = row.querySelector(
    ".urlbarView-dynamic-addons-description"
  );
  Assert.equal(description.textContent, merinoSuggestion.description);
  const ratingContainer = row.querySelector(
    ".urlbarView-dynamic-addons-ratingContainer"
  );
  Assert.ok(BrowserTestUtils.is_hidden(ratingContainer));
  const reviews = row.querySelector(".urlbarView-dynamic-addons-reviews");
  Assert.equal(reviews.textContent, "Recommended");

  await cleanUpNimbus();
  await SpecialPowers.popPrefEnv();
});

async function doShowLessFrequently({ input, expected }) {
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    value: input,
  });

  if (!expected.isSuggestionShown) {
    for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
      const details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
      Assert.notEqual(
        details.result.payload.dynamicType,
        "addons",
        `Addons suggestion should be absent (checking index ${i})`
      );
    }

    return;
  }

  const resultIndex = 1;
  const details = await UrlbarTestUtils.getDetailsOfResultAt(
    window,
    resultIndex
  );
  Assert.equal(
    details.result.payload.dynamicType,
    "addons",
    `Addons suggestion should be present at expected index after ${input} search`
  );

  // Click the command.
  try {
    await UrlbarTestUtils.openResultMenuAndClickItem(
      window,
      "show_less_frequently",
      {
        resultIndex,
      }
    );
    Assert.ok(expected.isMenuItemShown);
    Assert.ok(
      gURLBar.view.isOpen,
      "The view should remain open clicking the command"
    );
    Assert.ok(
      details.element.row.hasAttribute("feedback-acknowledgment"),
      "Row should have feedback acknowledgment after clicking command"
    );
  } catch (e) {
    Assert.ok(!expected.isMenuItemShown);
    Assert.ok(
      !details.element.row.hasAttribute("feedback-acknowledgment"),
      "Row should not have feedback acknowledgment after clicking command"
    );
    Assert.equal(
      e.message,
      "Menu item not found for command: show_less_frequently"
    );
  }

  await UrlbarTestUtils.promisePopupClose(window);
}

async function doDismissTest(command) {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.addons.featureGate", true]],
  });

  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    value: "123",
  });

  const resultCount = UrlbarTestUtils.getResultCount(window);
  const resultIndex = 1;
  let details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex);
  Assert.equal(
    details.result.payload.dynamicType,
    "addons",
    "Addons suggestion should be present"
  );

  // Sanity check.
  Assert.ok(UrlbarPrefs.get("suggest.addons"));

  // Click the command.
  await UrlbarTestUtils.openResultMenuAndClickItem(
    window,
    ["[data-l10n-id=firefox-suggest-command-dont-show-this]", command],
    { resultIndex, openByMouse: true }
  );

  Assert.ok(
    !UrlbarPrefs.get("suggest.addons"),
    "suggest.addons pref should be set to false after dismissal"
  );

  // The row should be a tip now.
  Assert.ok(gURLBar.view.isOpen, "The view should remain open after dismissal");
  Assert.equal(
    UrlbarTestUtils.getResultCount(window),
    resultCount,
    "The result count should not haved changed after dismissal"
  );
  details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex);
  Assert.equal(
    details.type,
    UrlbarUtils.RESULT_TYPE.TIP,
    "Row should be a tip after dismissal"
  );
  Assert.equal(
    details.result.payload.type,
    "dismissalAcknowledgment",
    "Tip type should be dismissalAcknowledgment"
  );
  Assert.ok(
    !details.element.row.hasAttribute("feedback-acknowledgment"),
    "Row should not have feedback acknowledgment after dismissal"
  );

  // Get the dismissal acknowledgment's "Got it" button and click it.
  let gotItButton = UrlbarTestUtils.getButtonForResultIndex(
    window,
    "0",
    resultIndex
  );
  Assert.ok(gotItButton, "Row should have a 'Got it' button");
  EventUtils.synthesizeMouseAtCenter(gotItButton, {}, window);

  // The view should remain open and the tip row should be gone.
  Assert.ok(
    gURLBar.view.isOpen,
    "The view should remain open clicking the 'Got it' button"
  );
  Assert.equal(
    UrlbarTestUtils.getResultCount(window),
    resultCount - 1,
    "The result count should be one less after clicking 'Got it' button"
  );
  for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
    details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
    Assert.ok(
      details.type != UrlbarUtils.RESULT_TYPE.TIP &&
        details.result.payload.dynamicType !== "addons",
      "Tip result and addon result should not be present"
    );
  }

  await UrlbarTestUtils.promisePopupClose(window);

  await SpecialPowers.popPrefEnv();
  UrlbarPrefs.clear("suggest.addons");
}
