import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { RecommendationProvider } from "lib/RecommendationProvider.sys.mjs";
import { combineReducers, createStore } from "redux";
import { reducers } from "common/Reducers.sys.mjs";
import { GlobalOverrider } from "test/unit/utils";

import { PersonalityProvider } from "lib/PersonalityProvider/PersonalityProvider.sys.mjs";
import { PersistentCache } from "lib/PersistentCache.sys.mjs";

const PREF_PERSONALIZATION_ENABLED = "discoverystream.personalization.enabled";
const PREF_PERSONALIZATION_MODEL_KEYS =
  "discoverystream.personalization.modelKeys";
describe("RecommendationProvider", () => {
  let feed;
  let sandbox;
  let clock;
  let globals;

  beforeEach(() => {
    globals = new GlobalOverrider();
    globals.set({
      PersistentCache,
      PersonalityProvider,
    });

    sandbox = sinon.createSandbox();
    clock = sinon.useFakeTimers();
    feed = new RecommendationProvider();
    feed.store = createStore(combineReducers(reducers), {});
  });

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

  describe("#setProvider", () => {
    it("should setup proper provider with modelKeys", async () => {
      feed.setProvider();

      assert.equal(feed.provider.modelKeys, undefined);

      feed.provider = null;
      feed._modelKeys = "1234";

      feed.setProvider();

      assert.equal(feed.provider.modelKeys, "1234");
      feed._modelKeys = "12345";

      // Calling it again should not rebuild the provider.
      feed.setProvider();
      assert.equal(feed.provider.modelKeys, "1234");
    });
  });

  describe("#calculateItemRelevanceScore", () => {
    it("should use personalized score with provider", async () => {
      const item = {};
      feed.provider = {
        calculateItemRelevanceScore: async () => 0.5,
      };
      await feed.calculateItemRelevanceScore(item);
      assert.equal(item.score, 0.5);
    });
  });

  describe("#teardown", () => {
    it("should call provider.teardown ", () => {
      sandbox.stub(global.Services.obs, "removeObserver").returns();
      feed.loaded = true;
      feed.provider = {
        teardown: sandbox.stub().resolves(),
      };
      feed.teardown();
      assert.calledOnce(feed.provider.teardown);
      assert.calledOnce(global.Services.obs.removeObserver);
      assert.calledWith(global.Services.obs.removeObserver, feed, "idle-daily");
    });
  });

  describe("#resetState", () => {
    it("should null affinityProviderV2 and affinityProvider", () => {
      feed._modelKeys = {};
      feed.provider = {};

      feed.resetState();

      assert.equal(feed._modelKeys, null);
      assert.equal(feed.provider, null);
    });
  });

  describe("#onAction: DISCOVERY_STREAM_CONFIG_CHANGE", () => {
    it("should call teardown, resetState, and setVersion", async () => {
      sandbox.spy(feed, "teardown");
      sandbox.spy(feed, "resetState");
      feed.onAction({
        type: at.DISCOVERY_STREAM_CONFIG_CHANGE,
      });
      assert.calledOnce(feed.teardown);
      assert.calledOnce(feed.resetState);
    });
  });

  describe("#onAction: PREF_CHANGED", () => {
    beforeEach(() => {
      sandbox.spy(feed.store, "dispatch");
    });
    it("should dispatch to DISCOVERY_STREAM_CONFIG_RESET PREF_PERSONALIZATION_MODEL_KEYS", async () => {
      feed.onAction({
        type: at.PREF_CHANGED,
        data: {
          name: PREF_PERSONALIZATION_MODEL_KEYS,
        },
      });

      assert.calledWith(
        feed.store.dispatch,
        ac.BroadcastToContent({
          type: at.DISCOVERY_STREAM_CONFIG_RESET,
        })
      );
    });
  });

  describe("#personalizationOverride", () => {
    it("should dispatch setPref", async () => {
      sandbox.spy(feed.store, "dispatch");
      feed.store.getState = () => ({
        Prefs: {
          values: {
            "discoverystream.personalization.enabled": true,
          },
        },
      });

      feed.personalizationOverride(true);

      assert.calledWithMatch(feed.store.dispatch, {
        data: {
          name: "discoverystream.personalization.override",
          value: true,
        },
        type: at.SET_PREF,
      });
    });
    it("should dispatch CLEAR_PREF", async () => {
      sandbox.spy(feed.store, "dispatch");
      feed.store.getState = () => ({
        Prefs: {
          values: {
            "discoverystream.personalization.enabled": true,
            "discoverystream.personalization.override": true,
          },
        },
      });

      feed.personalizationOverride(false);

      assert.calledWithMatch(feed.store.dispatch, {
        data: {
          name: "discoverystream.personalization.override",
        },
        type: at.CLEAR_PREF,
      });
    });
  });

  describe("#onAction: DISCOVERY_STREAM_DEV_IDLE_DAILY", () => {
    it("should trigger idle-daily observer", async () => {
      sandbox.stub(global.Services.obs, "notifyObservers").returns();
      await feed.onAction({
        type: at.DISCOVERY_STREAM_DEV_IDLE_DAILY,
      });
      assert.calledWith(
        global.Services.obs.notifyObservers,
        null,
        "idle-daily"
      );
    });
  });

  describe("#onAction: INIT", () => {
    it("should ", async () => {
      sandbox.stub(feed, "enable").returns();
      await feed.onAction({
        type: at.INIT,
      });
      assert.calledOnce(feed.enable);
      assert.calledWith(feed.enable, true);
    });
  });

  describe("#onAction: DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE", () => {
    it("should ", async () => {
      sandbox.stub(feed, "personalizationOverride").returns();
      await feed.onAction({
        type: at.DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE,
        data: { override: true },
      });
      assert.calledOnce(feed.personalizationOverride);
      assert.calledWith(feed.personalizationOverride, true);
    });
  });

  describe("#loadPersonalizationScoresCache", () => {
    it("should create a personalization provider from cached scores", async () => {
      sandbox.spy(feed.store, "dispatch");
      sandbox.spy(feed.cache, "set");
      feed.provider = {
        init: async () => {},
        getScores: () => "scores",
      };
      feed.store.getState = () => ({
        Prefs: {
          values: {
            pocketConfig: {
              recsPersonalized: true,
              spocsPersonalized: true,
            },
            "discoverystream.personalization.enabled": true,
            "feeds.section.topstories": true,
            "feeds.system.topstories": true,
          },
        },
      });
      const fakeCache = {
        personalization: {
          scores: 123,
          _timestamp: 456,
        },
      };
      sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));

      await feed.loadPersonalizationScoresCache();

      assert.equal(feed.personalizationLastUpdated, 456);
    });
  });

  describe("#updatePersonalizationScores", () => {
    beforeEach(() => {
      sandbox.spy(feed.store, "dispatch");
      sandbox.spy(feed.cache, "set");
      sandbox.spy(feed, "setProvider");
      feed.provider = {
        init: async () => {},
        getScores: () => "scores",
      };
    });
    it("should update provider on updatePersonalizationScores", async () => {
      feed.store.getState = () => ({
        Prefs: {
          values: {
            pocketConfig: {
              recsPersonalized: true,
              spocsPersonalized: true,
            },
            "discoverystream.personalization.enabled": true,
            "feeds.section.topstories": true,
            "feeds.system.topstories": true,
          },
        },
      });

      await feed.updatePersonalizationScores();

      assert.calledWith(
        feed.store.dispatch,
        ac.BroadcastToContent({
          type: at.DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED,
          data: {
            lastUpdated: 0,
          },
        })
      );
      assert.calledWith(feed.cache.set, "personalization", {
        scores: "scores",
        _timestamp: 0,
      });
    });
    it("should not update provider on updatePersonalizationScores", async () => {
      feed.store.getState = () => ({
        Prefs: {
          values: {
            "discoverystream.spocs.personalized": true,
            "discoverystream.recs.personalized": true,
            "discoverystream.personalization.enabled": false,
          },
        },
      });
      await feed.updatePersonalizationScores();

      assert.notCalled(feed.setProvider);
    });
  });

  describe("#onAction: DISCOVERY_STREAM_PERSONALIZATION_TOGGLE", () => {
    it("should fire SET_PREF with enabled", async () => {
      sandbox.spy(feed.store, "dispatch");
      feed.store.getState = () => ({
        Prefs: {
          values: {
            [PREF_PERSONALIZATION_ENABLED]: false,
          },
        },
      });

      await feed.onAction({
        type: at.DISCOVERY_STREAM_PERSONALIZATION_TOGGLE,
      });
      assert.calledWith(
        feed.store.dispatch,
        ac.SetPref(PREF_PERSONALIZATION_ENABLED, true)
      );
    });
  });

  describe("#observe", () => {
    it("should call updatePersonalizationScores on idle daily", async () => {
      sandbox.stub(feed, "updatePersonalizationScores").returns();
      feed.observe(null, "idle-daily");
      assert.calledOnce(feed.updatePersonalizationScores);
    });
  });
});
