/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AIWindow:
    "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs",
  IntentClassifier:
    "moz-src:///browser/components/aiwindow/models/IntentClassifier.sys.mjs",
  UrlbarProviderAiChat:
    "moz-src:///browser/components/urlbar/UrlbarProviderAiChat.sys.mjs",
  UrlbarSearchUtils:
    "moz-src:///browser/components/urlbar/UrlbarSearchUtils.sys.mjs",
});

const fakeEngine = {
  run({ args: [[query]] }) {
    const searchKeywords = ["search", "find"];
    const formattedPrompt = query.toLowerCase();
    const isSearch = searchKeywords.some(keyword =>
      formattedPrompt.includes(keyword)
    );

    // Simulate model confidence scores
    if (isSearch) {
      return [
        { label: "search", score: 0.95 },
        { label: "chat", score: 0.05 },
      ];
    }
    return [
      { label: "chat", score: 0.95 },
      { label: "search", score: 0.05 },
    ];
  },
};
let gEngineStub;

add_task(async function setup() {
  gEngineStub = sinon
    .stub(lazy.IntentClassifier, "_createEngine")
    .resolves(fakeEngine);
  registerCleanupFunction(() => {
    sinon.restore();
  });

  await lazy.UrlbarSearchUtils.init();

  // Set required prefs
  Services.prefs.setBoolPref("browser.ml.enable", true);
});

add_task(async function test_isActive() {
  let aiWindowStub = sinon
    .stub(lazy.AIWindow, "isAIWindowActiveAndEnabled")
    .returns(false);
  const provider = new lazy.UrlbarProviderAiChat();
  let controller = UrlbarTestUtils.newMockController();

  Assert.ok(
    !(await provider.isActive(createContext("hello"), controller)),
    "Provider is not active as this is not a Smart Window"
  );

  aiWindowStub.returns(true);
  Assert.ok(
    await provider.isActive(createContext("hello"), controller),
    "Provider is active"
  );

  Assert.ok(
    !(await provider.isActive(
      createContext("hello world", {
        searchMode: { engineName: "testEngine" },
      }),
      controller
    )),
    "Provider is not active as we're in Search Mode"
  );

  Assert.ok(
    !(await provider.isActive(createContext("hi"), controller)),
    "Provider is not active as prompt is too short"
  );
});

add_task(async function test_startQuery() {
  const provider = new lazy.UrlbarProviderAiChat();
  let engine = lazy.UrlbarSearchUtils.getDefaultEngine();
  let icon = await engine.getIconURL();

  const TESTS = [
    {
      desc: "Urlbar for 'chat' intent returns non-heuristic with suggestedIndex",
      sapName: "urlbar",
      input: "tell me a joke",
      expectedResults: [
        {
          heuristic: false,
          type: UrlbarUtils.RESULT_TYPE.AI_CHAT,
          suggestedIndex: 1,
          query: "tell me a joke",
          icon: lazy.UrlbarProviderAiChat.CHAT_ICON_URL,
          title: "tell me a joke",
        },
      ],
    },
    {
      desc: "Urlbar for 'search' intent doesn't return any result",
      sapName: "urlbar",
      input: "search for cute cat pictures",
      expectedResults: [],
    },
    {
      desc: "Urlbar for 'navigate' intent doesn't return any result",
      sapName: "urlbar",
      input: "mozilla.org",
      expectedResults: [],
    },
    {
      desc: "Smartbar for 'search' intent returns a non heuristic result",
      sapName: "smartbar",
      input: "search for cute cat pictures",
      expectedResults: [
        {
          heuristic: false,
          type: UrlbarUtils.RESULT_TYPE.AI_CHAT,
          suggestedIndex: 1,
          query: "search for cute cat pictures",
          icon: lazy.UrlbarProviderAiChat.CHAT_ICON_URL,
          title: "search for cute cat pictures",
        },
      ],
    },
    {
      desc: "Smartbar for 'chat' intent returns heuristic and search result",
      sapName: "smartbar",
      input: "tell me a joke",
      expectedResults: [
        {
          heuristic: true,
          type: UrlbarUtils.RESULT_TYPE.AI_CHAT,
          suggestedIndex: undefined,
          query: "tell me a joke",
          icon: lazy.UrlbarProviderAiChat.CHAT_ICON_URL,
          title: "tell me a joke",
        },
        {
          heuristic: false,
          type: UrlbarUtils.RESULT_TYPE.SEARCH,
          suggestedIndex: 1,
          query: "tell me a joke",
          icon,
          engine: engine.name,
          title: "tell me a joke",
        },
      ],
    },
    {
      desc: "Smartbar for 'navigate' intent doesn't return any result",
      sapName: "smartbar",
      input: "mozilla.org",
      expectedResults: [],
    },
  ];

  for (const { desc, sapName, input, expectedResults } of TESTS) {
    info(desc);

    let added = [];
    await provider.startQuery(
      createContext(input, {
        sapName,
      }),
      (_provider, result) => {
        added.push(result);
      }
    );

    Assert.equal(
      added.length,
      expectedResults.length,
      "Check number of results"
    );

    for (let i = 0; i < expectedResults.length; i++) {
      let result = added[i];
      let expected = expectedResults[i];

      for (let prop in expected) {
        let checkFn =
          expected[prop] === undefined
            ? Assert.strictEqual.bind(Assert)
            : Assert.equal.bind(Assert);
        let checkObj = ["engine", "icon", "query", "title"].includes(prop)
          ? result.payload
          : result;

        checkFn(
          checkObj[prop],
          expected[prop],
          `Check result ${i} property ${prop}`
        );
      }
    }
  }
});

add_task(async function test_intentClassifierThrows() {
  const provider = new lazy.UrlbarProviderAiChat();

  info("Stub the engine to throw, we should fallback to 'search' intent.");
  const throwingEngine = {
    run() {
      throw new Error("Intent classifier failure");
    },
  };
  gEngineStub.resolves(throwingEngine);

  let added = [];
  await provider.startQuery(
    createContext("tell me a joke", {
      sapName: "smartbar",
    }),
    (_provider, result) => {
      added.push(result);
    }
  );

  Assert.equal(
    added.length,
    1,
    "One result is returned, because we fallback to 'search' intent"
  );
  let result = added[0];
  Assert.equal(
    result.payload.query,
    "tell me a joke",
    "The result has the expected query"
  );
  Assert.equal(
    result.type,
    UrlbarUtils.RESULT_TYPE.AI_CHAT,
    "The result is a chat result"
  );
  Assert.equal(
    result.suggestedIndex,
    1,
    "The result is non-heuristic (suggestedIndex is set)"
  );
  Assert.equal(
    result.heuristic,
    false,
    "The result is non-heuristic (heuristic is false)"
  );
});
