import * as R from "ramda";

const {
  favoritesListener,
  isFavorite,
  setAsFavorite,
  removeFromFavorites,
  getAllFavorites,
} = require("../index");

const { localStorage } = require("../../ZappStorage/LocalStorage");
const namespace = "local_favourites";

const REMOVE_ERROR = new Error(
  "entry with id I throw when retrieved is not in the favourites list"
);

const { bridgeLogger } = require("../../logger");

const GET_ERROR = new Error("could not get item");
const SET_ERROR = new Error("could not set item");

const item = {
  id: "A123",
  foo: "bar",
};

const item2 = {
  id: "B456",
  foo: "baz",
};

const itemThatThrowsGet = {
  id: "I throw when retrieved",
};

const itemThatThrowsSet = {
  id: "I throw when set",
};

jest.mock("../../ZappStorage/LocalStorage", () => {
  let storage = {};

  return {
    localStorage: {
      setItem: jest.fn((key, value, namespace) => {
        if (R.includes(itemThatThrowsSet, value)) {
          throw SET_ERROR;
        }

        if (!storage[namespace]) {
          storage[namespace] = {};
        }

        storage[namespace][key] = value;

        return Promise.resolve(true);
      }),
      getItem: jest.fn((key, namespace) => {
        const values = storage[namespace]?.[key] || [];

        if (R.includes(itemThatThrowsGet, values)) {
          throw GET_ERROR;
        }

        return Promise.resolve(values);
      }),
      __reset: () => {
        storage = {};
      },
    },
  };
});

jest.mock("../../logger", () => ({
  bridgeLogger: {
    error: jest.fn(),
  },
}));

beforeEach(() => {
  bridgeLogger.error.mockClear();
});

describe("isFavorite", () => {
  beforeEach(() => {
    localStorage.__reset();
    localStorage.setItem("favourites", [item], namespace);
  });

  it("rejects if an error is thrown", async () => {
    localStorage.setItem("favourites", [itemThatThrowsGet], namespace);
    await expect(isFavorite(itemThatThrowsGet)).rejects.toEqual(GET_ERROR);
    expect(bridgeLogger.error).toHaveBeenCalledWith(GET_ERROR);
  });

  it("resolves to true if the item is in the favorites", async () => {
    return expect(isFavorite(item)).resolves.toBe(true);
  });

  it("resolves to false if the item is not in the favorites", async () => {
    return expect(isFavorite(item2)).resolves.toBe(false);
  });
});

describe("setAsFavorite", () => {
  beforeEach(() => {
    localStorage.__reset();
  });

  it("rejects when it throws", async () => {
    await expect(setAsFavorite(itemThatThrowsSet)).rejects.toEqual(SET_ERROR);
    expect(bridgeLogger.error).toHaveBeenCalledWith(SET_ERROR);
  });

  it("sets the item as favorites", async () => {
    await expect(setAsFavorite(item)).resolves.toEqual(
      expect.arrayContaining([item])
    );

    return expect(isFavorite(item)).resolves.toBe(true);
  });
});

describe("removeFromFavorites", () => {
  beforeEach(() => {
    localStorage.__reset();
    localStorage.setItem("favourites", [item], namespace);
  });

  it("rejects when it throws", async () => {
    localStorage.setItem("favourites", itemThatThrowsGet, namespace);

    await expect(removeFromFavorites(itemThatThrowsGet)).rejects.toEqual(
      REMOVE_ERROR
    );

    expect(bridgeLogger.error).toHaveBeenCalledWith(REMOVE_ERROR);
  });

  it("removes the item from the favorites", async () => {
    await expect(isFavorite(item)).resolves.toBe(true);
    await expect(removeFromFavorites(item)).resolves.not.toContain(item);

    return expect(isFavorite(item)).resolves.toBe(false);
  });
});

describe("getAllFavorites", () => {
  beforeEach(() => {
    localStorage.__reset();
    localStorage.setItem("favourites", [item, item2], namespace);
  });

  it("rejects when it throws", async () => {
    localStorage.setItem("favourites", [itemThatThrowsGet], namespace);
    await expect(getAllFavorites()).rejects.toEqual(GET_ERROR);
    expect(bridgeLogger.error).toHaveBeenCalledWith(GET_ERROR);
  });

  it("returns all favorites", async () => {
    return expect(getAllFavorites()).resolves.toMatchSnapshot();
  });
});

describe("favouritesListener", () => {
  beforeEach(() => {
    localStorage.__reset();
  });

  it("allows to register a listener called when favourites are changed", async () => {
    const listener = jest.fn();
    favoritesListener.on("FAVORITES_CHANGED", listener);
    await setAsFavorite(item);

    return expect(listener).toHaveBeenCalledWith([item], expect.any(Function));
  });
});
