import { GlobalOverrider } from "tests/unit/utils";
import { PanelTestProvider } from "modules/PanelTestProvider.sys.mjs";
import { _MomentsPageHub } from "modules/MomentsPageHub.sys.mjs";
const HOMEPAGE_OVERRIDE_PREF = "browser.startup.homepage_override.once";

describe("MomentsPageHub", () => {
  let globals;
  let sandbox;
  let instance;
  let handleMessageRequestStub;
  let addImpressionStub;
  let blockMessageByIdStub;
  let sendTelemetryStub;
  let getStringPrefStub;
  let setStringPrefStub;
  let setIntervalStub;
  let clearIntervalStub;

  beforeEach(async () => {
    globals = new GlobalOverrider();
    sandbox = sinon.createSandbox();
    instance = new _MomentsPageHub();
    const messages = (await PanelTestProvider.getMessages()).filter(
      ({ template }) => template === "update_action"
    );
    handleMessageRequestStub = sandbox.stub().resolves(messages);
    addImpressionStub = sandbox.stub();
    blockMessageByIdStub = sandbox.stub();
    getStringPrefStub = sandbox.stub();
    setStringPrefStub = sandbox.stub();
    setIntervalStub = sandbox.stub();
    clearIntervalStub = sandbox.stub();
    sendTelemetryStub = sandbox.stub();
    globals.set({
      setInterval: setIntervalStub,
      clearInterval: clearIntervalStub,
      Services: {
        prefs: {
          getStringPref: getStringPrefStub,
          setStringPref: setStringPrefStub,
        },
      },
      Glean: {
        messagingExperiments: {
          reachMomentsPage: {
            record: () => {},
          },
        },
        messagingSystem: {
          messageRequestTime: {
            start() {},
            stopAndAccumulate() {},
          },
        },
      },
    });
  });

  afterEach(() => {
    sandbox.restore();
    globals.restore();
  });

  it("should create an instance", async () => {
    setIntervalStub.returns(42);
    assert.ok(instance);
    await instance.init(Promise.resolve(), {
      handleMessageRequest: handleMessageRequestStub,
      addImpression: addImpressionStub,
      blockMessageById: blockMessageByIdStub,
    });
    assert.equal(instance.state._intervalId, 42);
  });

  it("should init only once", async () => {
    assert.notCalled(handleMessageRequestStub);

    await instance.init(Promise.resolve(), {
      handleMessageRequest: handleMessageRequestStub,
      addImpression: addImpressionStub,
      blockMessageById: blockMessageByIdStub,
    });
    await instance.init(Promise.resolve(), {
      handleMessageRequest: handleMessageRequestStub,
      addImpression: addImpressionStub,
      blockMessageById: blockMessageByIdStub,
    });

    assert.calledOnce(handleMessageRequestStub);

    instance.uninit();

    await instance.init(Promise.resolve(), {
      handleMessageRequest: handleMessageRequestStub,
      addImpression: addImpressionStub,
      blockMessageById: blockMessageByIdStub,
    });

    assert.calledTwice(handleMessageRequestStub);
  });

  it("should uninit the instance", () => {
    instance.uninit();
    assert.calledOnce(clearIntervalStub);
  });

  it("should setInterval for `checkHomepageOverridePref`", async () => {
    await instance.init(sandbox.stub().resolves(), {});
    sandbox.stub(instance, "checkHomepageOverridePref");

    assert.calledOnce(setIntervalStub);
    assert.calledWithExactly(setIntervalStub, sinon.match.func, 5 * 60 * 1000);

    assert.notCalled(instance.checkHomepageOverridePref);
    const [cb] = setIntervalStub.firstCall.args;

    cb();

    assert.calledOnce(instance.checkHomepageOverridePref);
  });

  describe("#messageRequest", () => {
    beforeEach(async () => {
      await instance.init(Promise.resolve(), {
        handleMessageRequest: handleMessageRequestStub,
        addImpression: addImpressionStub,
        blockMessageById: blockMessageByIdStub,
        sendTelemetry: sendTelemetryStub,
      });
    });
    afterEach(() => {
      instance.uninit();
    });
    it("should fetch a message with the provided trigger and template", async () => {
      await instance.messageRequest({
        triggerId: "trigger",
        template: "template",
      });

      assert.calledTwice(handleMessageRequestStub);
      assert.calledWithExactly(handleMessageRequestStub, {
        triggerId: "trigger",
        template: "template",
        returnAll: true,
      });
    });
    it("shouldn't do anything if no message is provided", async () => {
      // Reset the call from `instance.init`
      setStringPrefStub.reset();
      handleMessageRequestStub.resolves([]);
      await instance.messageRequest({ triggerId: "trigger" });

      assert.notCalled(setStringPrefStub);
    });
    it("should record a message request time", async () => {
      const fakeTimerId = 42;
      const start = sandbox
        .stub(global.Glean.messagingSystem.messageRequestTime, "start")
        .returns(fakeTimerId);
      const stopAndAccumulate = sandbox.stub(
        global.Glean.messagingSystem.messageRequestTime,
        "stopAndAccumulate"
      );

      await instance.messageRequest({ triggerId: "trigger" });

      assert.calledOnce(start);
      assert.calledWithExactly(start);
      assert.calledOnce(stopAndAccumulate);
      assert.calledWithExactly(stopAndAccumulate, fakeTimerId);
    });
    it("should record Reach event for the Moments page experiment", async () => {
      const momentsMessages = (await PanelTestProvider.getMessages()).filter(
        ({ template }) => template === "update_action"
      );
      const messages = [
        {
          forReachEvent: { sent: false },
          experimentSlug: "foo",
          branchSlug: "bar",
        },
        ...momentsMessages,
      ];
      handleMessageRequestStub.resolves(messages);
      sandbox.spy(global.Glean.messagingExperiments.reachMomentsPage, "record");
      sandbox.spy(instance, "executeAction");

      await instance.messageRequest({ triggerId: "trigger" });

      assert.calledOnce(
        global.Glean.messagingExperiments.reachMomentsPage.record
      );
      assert.calledOnce(instance.executeAction);
    });
    it("should not record the Reach event if it's already sent", async () => {
      const messages = [
        {
          forReachEvent: { sent: true },
          experimentSlug: "foo",
          branchSlug: "bar",
        },
      ];
      handleMessageRequestStub.resolves(messages);
      sandbox.spy(global.Glean.messagingExperiments.reachMomentsPage, "record");

      await instance.messageRequest({ triggerId: "trigger" });

      assert.notCalled(
        global.Glean.messagingExperiments.reachMomentsPage.record
      );
    });
    it("should not trigger the action if it's only for the Reach event", async () => {
      const messages = [
        {
          forReachEvent: { sent: false },
          experimentSlug: "foo",
          branchSlug: "bar",
        },
      ];
      handleMessageRequestStub.resolves(messages);
      sandbox.spy(global.Glean.messagingExperiments.reachMomentsPage, "record");
      sandbox.spy(instance, "executeAction");

      await instance.messageRequest({ triggerId: "trigger" });

      assert.calledOnce(
        global.Glean.messagingExperiments.reachMomentsPage.record
      );
      assert.notCalled(instance.executeAction);
    });
  });
  describe("executeAction", () => {
    beforeEach(async () => {
      blockMessageByIdStub = sandbox.stub();
      await instance.init(sandbox.stub().resolves(), {
        addImpression: addImpressionStub,
        blockMessageById: blockMessageByIdStub,
        sendTelemetry: sendTelemetryStub,
      });
    });
    it("should set HOMEPAGE_OVERRIDE_PREF on `moments-wnp` action", async () => {
      const [msg] = await handleMessageRequestStub();
      sandbox.useFakeTimers();
      instance.executeAction(msg);

      assert.calledOnce(setStringPrefStub);
      assert.calledWithExactly(
        setStringPrefStub,
        HOMEPAGE_OVERRIDE_PREF,
        JSON.stringify({
          message_id: msg.id,
          url: msg.content.action.data.url,
          expire: instance.getExpirationDate(
            msg.content.action.data.expireDelta
          ),
        })
      );
    });
    it("should block after taking the action", async () => {
      const [msg] = await handleMessageRequestStub();
      instance.executeAction(msg);

      assert.calledOnce(blockMessageByIdStub);
      assert.calledWithExactly(blockMessageByIdStub, msg.id);
    });
    it("should compute expire based on expireDelta", async () => {
      sandbox.spy(instance, "getExpirationDate");

      const [msg] = await handleMessageRequestStub();
      instance.executeAction(msg);

      assert.calledOnce(instance.getExpirationDate);
      assert.calledWithExactly(
        instance.getExpirationDate,
        msg.content.action.data.expireDelta
      );
    });
    it("should compute expire based on expireDelta", async () => {
      sandbox.spy(instance, "getExpirationDate");

      const [msg] = await handleMessageRequestStub();
      const msgWithExpire = {
        ...msg,
        content: {
          ...msg.content,
          action: {
            ...msg.content.action,
            data: { ...msg.content.action.data, expire: 41 },
          },
        },
      };
      instance.executeAction(msgWithExpire);

      assert.notCalled(instance.getExpirationDate);
      assert.calledOnce(setStringPrefStub);
      assert.calledWithExactly(
        setStringPrefStub,
        HOMEPAGE_OVERRIDE_PREF,
        JSON.stringify({
          message_id: msg.id,
          url: msg.content.action.data.url,
          expire: 41,
        })
      );
    });
    it("should send user telemetry", async () => {
      const [msg] = await handleMessageRequestStub();
      const sendUserEventTelemetrySpy = sandbox.spy(
        instance,
        "sendUserEventTelemetry"
      );
      instance.executeAction(msg);

      assert.calledOnce(sendTelemetryStub);
      assert.calledWithExactly(sendUserEventTelemetrySpy, msg);
      assert.calledWithExactly(sendTelemetryStub, {
        type: "MOMENTS_PAGE_TELEMETRY",
        data: {
          action: "moments_user_event",
          bucket_id: "WNP_THANK_YOU",
          event: "MOMENTS_PAGE_SET",
          message_id: "WNP_THANK_YOU",
        },
      });
    });
  });
  describe("#checkHomepageOverridePref", () => {
    let messageRequestStub;
    beforeEach(() => {
      messageRequestStub = sandbox.stub(instance, "messageRequest");
    });
    it("should catch parse errors", () => {
      getStringPrefStub.returns({});

      instance.checkHomepageOverridePref();

      assert.calledOnce(messageRequestStub);
      assert.calledWithExactly(messageRequestStub, {
        template: "update_action",
        triggerId: "momentsUpdate",
      });
    });
  });
});
