import * as R from "ramda";

import {
  favoritesListener,
  isFavorite,
  setAsFavorite,
  removeFromFavorites,
  getAllFavorites,
} from "..";

import { localStorage } from "../../ZappStorage/LocalStorage";
import { bridgeLogger } from "../../logger";

const namespace = "local_favourites";

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

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

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

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

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

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

let mockStorage = {};

const mockSetItem = jest.fn((key, value, namespace) => {
  if (R.includes(itemThatThrowsSet, value)) {
    throw SET_ERROR;
  }

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

  mockStorage[namespace][key] = value;

  return Promise.resolve(true);
});

const mockGetItem = jest.fn((key, namespace) => {
  const values = mockStorage[namespace]?.[key] || [];

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

  return Promise.resolve(values);
});

jest.spyOn(localStorage, "setItem").mockImplementation(mockSetItem);
jest.spyOn(localStorage, "getItem").mockImplementation(mockGetItem);

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

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

describe("isFavorite", () => {
  beforeEach(() => {
    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", () => {
  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.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.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", () => {
  it("allows to register a listener called when favourites are changed", async () => {
    const listener = jest.fn();
    favoritesListener.on("FAVORITES_CHANGED", listener);
    jest.clearAllMocks();
    await setAsFavorite({ ...item, id: "A1234" });

    return expect(listener).toHaveBeenCalledWith(
      [{ ...item, id: "A1234" }],
      expect.any(Function)
    );
  });
});
