import {GlobalOverrider} from "test/unit/utils";
import {UserDomainAffinityProvider} from "lib/UserDomainAffinityProvider.jsm";

const TIME_SEGMENTS = [
  {"id": "hour", "startTime": 3600, "endTime": 0, "weightPosition": 1},
  {"id": "day", "startTime": 86400, "endTime": 3600, "weightPosition": 0.75},
  {"id": "week", "startTime": 604800, "endTime": 86400, "weightPosition": 0.5},
  {"id": "weekPlus", "startTime": null, "endTime": 604800, "weightPosition": 0.25},
];

const PARAMETER_SETS = {
  "paramSet1": {
    "recencyFactor": 0.5,
    "frequencyFactor": 0.5,
    "combinedDomainFactor": 0.5,
    "perfectFrequencyVisits": 10,
    "perfectCombinedDomainScore": 2,
    "multiDomainBoost": 0.1,
    "itemScoreFactor": 0,
  },
  "paramSet2": {
    "recencyFactor": 1,
    "frequencyFactor": 0.7,
    "combinedDomainFactor": 0.8,
    "perfectFrequencyVisits": 10,
    "perfectCombinedDomainScore": 2,
    "multiDomainBoost": 0.1,
    "itemScoreFactor": 0,
  },
};

describe("User Domain Affinity Provider", () => {
  let instance;
  let globals;

  beforeEach(() => {
    globals = new GlobalOverrider();

    const testUrl = "www.somedomain.com";
    globals.sandbox.stub(global.Services.io, "newURI").returns({host: testUrl});

    globals.sandbox.stub(global.PlacesUtils.history, "executeQuery").returns({root: {childCount: 1, getChild: index => ({uri: testUrl, accessCount: 1})}});
    globals.sandbox.stub(global.PlacesUtils.history, "getNewQuery").returns({"TIME_RELATIVE_NOW": 1});
    globals.sandbox.stub(global.PlacesUtils.history, "getNewQueryOptions").returns({});

    instance = new UserDomainAffinityProvider(TIME_SEGMENTS, PARAMETER_SETS);
  });
  afterEach(() => {
    globals.restore();
  });
  describe("#init", () => {
    function calculateScore(visitCounts, timeSeg, domain, ps) {
      const vc = visitCounts[timeSeg][domain];
      const score = instance.calculateScore(vc * Number(ps.timeSegmentWeights[timeSeg]), ps.perfectFrequencyVisits, ps.frequencyFactor);
      return Math.min(1, score);
    }
    it("should create a UserDomainAffinityProvider", () => {
      assert.instanceOf(instance, UserDomainAffinityProvider);
    });
    it("should calculate time segment weights for parameter sets", () => {
      const expectedParamSets = Object.assign({}, PARAMETER_SETS);

      // Verify that parameter set specific recencyFactor was applied
      expectedParamSets.paramSet1.timeSegmentWeights = {"hour": 1, "day": 0.75, "week": 0.5, "weekPlus": 0.25};
      expectedParamSets.paramSet2.timeSegmentWeights = {"hour": 1, "day": 1, "week": 1, "weekPlus": 1};
      assert.deepEqual(expectedParamSets, instance.parameterSets);
    });
    it("should calculate user domain affinity scores", () => {
      const ps1 = instance.parameterSets.paramSet1;
      const ps2 = instance.parameterSets.paramSet2;

      const visitCounts = {
        "hour": {"a.com": 1, "b.com": 2},
        "day": {"a.com": 4},
        "week": {"c.com": 1},
        "weekPlus": {"a.com": 1, "d.com": 3},
      };
      instance.queryVisits = ts => visitCounts[ts.id];

      const expScoreAHourPs1 = calculateScore(visitCounts, "hour", "a.com", ps1);
      const expScoreBHourPs1 = calculateScore(visitCounts, "hour", "b.com", ps1);
      const expScoreAHourPs2 = calculateScore(visitCounts, "hour", "a.com", ps2);
      const expScoreBHourPs2 = calculateScore(visitCounts, "hour", "b.com", ps2);
      const expScoreADayPs1 = calculateScore(visitCounts, "day", "a.com", ps1);
      const expScoreADayPs2 = calculateScore(visitCounts, "day", "a.com", ps2);
      const expScoreCWeekPs1 = calculateScore(visitCounts, "week", "c.com", ps1);
      const expScoreCWeekPs2 = calculateScore(visitCounts, "week", "c.com", ps2);
      const expScoreAWeekPlusPs1 = calculateScore(visitCounts, "weekPlus", "a.com", ps1);
      const expScoreAWeekPlusPs2 = calculateScore(visitCounts, "weekPlus", "a.com", ps2);
      const expScoreDWeekPlusPs1 = calculateScore(visitCounts, "weekPlus", "d.com", ps1);
      const expScoreDWeekPlusPs2 = calculateScore(visitCounts, "weekPlus", "d.com", ps2);
      const expectedScores = {
        "a.com": {
          "paramSet1": Math.min(1, expScoreAHourPs1 + expScoreADayPs1 + expScoreAWeekPlusPs1),
          "paramSet2": Math.min(1, expScoreAHourPs2 + expScoreADayPs2 + expScoreAWeekPlusPs2),
        },
        "b.com": {"paramSet1": expScoreBHourPs1, "paramSet2": expScoreBHourPs2},
        "c.com": {"paramSet1": expScoreCWeekPs1, "paramSet2": expScoreCWeekPs2},
        "d.com": {"paramSet1": expScoreDWeekPlusPs1, "paramSet2": expScoreDWeekPlusPs2},
      };

      const scores = instance.calculateAllUserDomainAffinityScores();
      assert.deepEqual(expectedScores, scores);
    });
    it("should return domain affinities", () => {
      const scores = {
        "a.com": {
          "paramSet1": 1,
          "paramSet2": 0.9,
        },
      };
      instance = new UserDomainAffinityProvider(TIME_SEGMENTS, PARAMETER_SETS, 100, "v1", scores);

      const expectedAffinities = {
        "timeSegments": TIME_SEGMENTS,
        "parameterSets": PARAMETER_SETS,
        "maxHistoryQueryResults": 100,
        "scores": scores,
        "version": "v1",
      };
      assert.deepEqual(instance.getAffinities(), expectedAffinities);
    });
  });
  describe("#score", () => {
    it("should calculate item relevance score", () => {
      const ps = instance.parameterSets.paramSet2;

      const visitCounts = {
        "hour": {"a.com": 1, "b.com": 2},
        "day": {"a.com": 4},
        "week": {"c.com": 1},
        "weekPlus": {"a.com": 1, "d.com": 3},
      };
      instance.queryVisits = ts => visitCounts[ts.id];
      instance.scores = instance.calculateAllUserDomainAffinityScores();

      const testItem = {
        "domain_affinities": {"a.com": 1},
        "item_score": 1,
        "parameter_set": "paramSet2",
      };
      const combinedDomainScore = instance.scores["a.com"].paramSet2 * Math.pow(ps.multiDomainBoost + 1, 1);
      const expectedItemScore = instance.calculateScore(combinedDomainScore,
        ps.perfectCombinedDomainScore,
        ps.combinedDomainFactor);

      const itemScore = instance.calculateItemRelevanceScore(testItem);
      assert.equal(expectedItemScore, itemScore);
    });
    it("should calculate relevance score equal to item_score if item has no domain affinities", () => {
      const testItem = {item_score: 0.985};
      const itemScore = instance.calculateItemRelevanceScore(testItem);
      assert.equal(testItem.item_score, itemScore);
    });
    it("should calculate scores with factor", () => {
      assert.equal(1, instance.calculateScore(2, 1, 0.5));
      assert.equal(0.5, instance.calculateScore(0.5, 1, 0.5));
      assert.isBelow(instance.calculateScore(0.5, 1, 0.49), 1);
      assert.isBelow(instance.calculateScore(0.5, 1, 0.51), 1);
    });
  });
});
