import * as redux from "react-redux";
import store from "../../store";
import { Router } from "react-router-dom";
import history from "../../store/utils/history";
import { mockState } from "../../mockState";
import { render } from "@testing-library/react";
import AdsPage from "../../pages/AdsPage/AdsPage";
import * as api from "../../request/adsRequest";
import { runSaga } from "redux-saga";
import { FETCH_ADS_REQ } from "../../store/actions/ads/adsActionConstants";
import ColorModeProvider from "../../context/ColorModeContext";
import * as fc from "../../store/saga/adsSaga";
import { setAds, setFilteredAds } from "../../store/actions/ads/adsAction";
import { setArchiveAds } from "../../store/actions/archiveAds/archiveAdsActions";
import { setCreateAd } from "../../store/actions/createAd/createAdActions";
import { setAd, setAdError } from "../../store/actions/ad/adActions";
import { archiveActiveAd } from "../../store/actions/archiveActiveAd/archiveActiveAdActions";
import * as helper from "../../util/helpers/rejectErrorCodeHelper";

describe("Ads reducer tests", () => {
  const cont = (
    <redux.Provider store={store}>
      <Router history={history}>
        <ColorModeProvider>
          <AdsPage />
        </ColorModeProvider>
      </Router>
    </redux.Provider>
  );

  let spyOnUseSelector;
  let spyOnUseDispatch;
  let mockDispatch;

  beforeEach(() => {
    spyOnUseSelector = jest.spyOn(redux, "useSelector");
    spyOnUseSelector.mockReturnValueOnce(mockState.ads);

    spyOnUseDispatch = jest.spyOn(redux, "useDispatch");

    mockDispatch = jest.fn();
    spyOnUseDispatch.mockReturnValue(mockDispatch);
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it("Should dispatch get ads request when rendered", () => {
    render(cont);
    expect(mockDispatch).toHaveBeenCalledWith({
      type: FETCH_ADS_REQ,
    });
  });

  it("Should load and handle ads in case of success", async () => {
    const dispatchedActions = [];

    const mockedCall = { data: mockState.ads.ads };
    api.getAllAds = jest.fn(() => Promise.resolve(mockedCall));

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, fc.getAds, {}).done;
    expect(api.getAllAds.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setAds(mockedCall.data));
  });

  it("Should load and handle filtered ads in case of success", async () => {
    const dispatchedActions = [];
    const filter = {
      minimumExperience: 0,
      maximumExperience: 0,
      technologies: [1],
      workHour: "FullTime",
      employmentType: "Work",
    };

    const filteredData = mockState.ads.ads.filter(
      (ad) =>
        ad.minimumExperience >= filter.minimumExperience &&
        ad.minimumExperience <= filter.maximumExperience &&
        ad.workHour === filter.workHour &&
        ad.employmentType === filter.employmentType
    );

    const mockedCall = { data: filteredData };
    api.getAllFilteredAds = jest.fn(() => Promise.resolve(mockedCall));

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, fc.getFilteredAds, filter).done;
    expect(api.getAllFilteredAds.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setFilteredAds(mockedCall.data));
  });

  it("Should load and handle archived ads in case of success", async () => {
    const dispatchedActions = [];
    const date = new Date();

    const filteredData = mockState.ads.ads.filter((ad) => ad.expiredAt < date);

    const mockedCall = { data: filteredData };
    api.getAllArchiveAds = jest.fn(() => Promise.resolve(mockedCall));

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, fc.getArchiveAds, {}).done;
    expect(api.getAllArchiveAds.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setArchiveAds(mockedCall.data));
  });

  it("Should load and handle ad by id in case of success", async () => {
    const dispatchedActions = [];
    const id = 1;

    const filteredData = mockState.ads.ads.filter((ad) => ad.id === id);

    const mockedCall = { data: filteredData };
    api.getAdDetailsById = jest.fn(() => Promise.resolve(mockedCall));

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, fc.getAd, { payload: id }).done;
    expect(api.getAdDetailsById.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setAd(mockedCall.data));
  });

  it("Should create ad", async () => {
    const dispatchedActions = [];

    const mockedCall = {
      data: {
        title: "React Developer",
        minimumExperience: 1,
        createdAt: new Date(),
        expiredAt: new Date("2023-5-5"),
        keyResponsibilities: "key responsibilities",
        requirements: "requirements",
        offer: "offer",
        workHour: "PartTime",
        employmentType: "Intership",
        technologiesIds: [1, 2],
        onSuccessAddAd: jest.fn,
      },
    };
    api.createNewAd = jest.fn(() => Promise.resolve(mockedCall));

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, fc.createAd, {
      payload: {
        title: "React Developer",
        minimumExperience: 1,
        createdAt: new Date(),
        expiredAt: new Date("2023-5-5"),
        keyResponsibilities: "key responsibilities",
        requirements: "requirements",
        offer: "offer",
        workHour: "PartTime",
        employmentType: "Intership",
        technologiesIds: [1, 2],
        onSuccessAddAd: jest.fn,
      },
    }).done;

    expect(api.createNewAd.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setCreateAd(mockedCall.data));
  });

  it("Archive ad", async () => {
    const dispatchedActions = [];

    const mockedCall = {
      data: {
        id: 1,
        navigateToAds: jest.fn,
      },
    };
    api.archiveActiveAdRequest = jest.fn(() => Promise.resolve(mockedCall));

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, fc.archiveActiveAdSaga, {
      payload: {
        id: 1,
        navigateToAds: jest.fn,
      },
    }).done;

    expect(api.archiveActiveAdRequest.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(archiveActiveAd(mockedCall.data));
  });

  it("Should not return ads when exception was thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: "Error" },
      },
    };
    api.getAllAds = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    // wait for saga to complete
    await runSaga(fakeStore, fc.getAds, {}).done;

    expect(mockfn).not.toHaveBeenCalled();
  });

  it("Should not return filtered ads when exception was thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: "Error" },
      },
    };
    const filter = {
      minimumExperience: 0,
      maximumExperience: 0,
      technologies: [1],
      workHour: "FullTime",
      employmentType: "Work",
    };

    api.getFilteredAds = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    // wait for saga to complete
    await runSaga(fakeStore, fc.getFilteredAds, filter).done;

    expect(mockfn).not.toHaveBeenCalled();
  });

  it("Should not return ad when exception was thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: "Error" },
      },
    };
    api.getAd = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    // wait for saga to complete
    await runSaga(fakeStore, fc.getAd, {
      payload: {
        id: 1,
      },
    }).done;

    expect(mockfn).not.toHaveBeenCalled();
  });

  it("Should not return archived ad when exception was thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: "Error" },
      },
    };
    api.getAd = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    // wait for saga to complete
    await runSaga(fakeStore, fc.getArchiveAds, {}).done;

    expect(mockfn).not.toHaveBeenCalled();
  });

  it("Should not create ad when exception was thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: "Error" },
      },
    };
    api.createNewAd = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    // wait for saga to complete
    await runSaga(fakeStore, fc.createAd, {
      payload: {
        title: "React Developer",
        minimumExperience: 1,
        createdAt: new Date(),
        expiredAt: new Date("2023-5-5"),
        keyResponsibilities: "key responsibilities",
        requirements: "requirements",
        offer: "offer",
        workHour: "PartTime",
        employmentType: "Intership",
        technologiesIds: [1, 2],
        onSuccessAddAd: jest.fn,
      },
    }).done;

    expect(mockfn).not.toHaveBeenCalled();
  });

  it("Should archive active ad when exception was thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: "Error" },
      },
    };
    api.createNewAd = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

    const fakeStore = {
      getState: () => mockState.ads.ads,
      dispatch: (action) => dispatchedActions.push(action),
    };

    // wait for saga to complete
    await runSaga(fakeStore, fc.archiveActiveAdSaga, {
      payload: {
        id: 1,
        navigateToAds: jest.fn,
      },
    }).done;

    expect(mockfn).not.toHaveBeenCalled();
  });

  it("should handle ad load errors in case of failure", async () => {
    const dispatchedActions = [];

    helper.rejectErrorCodeHelper = jest.fn(() => "Error");

    const error = {
      response: {
        data: { message: "Error" },
      },
    };

    api.getAd = jest.fn(() => Promise.reject(error));

    const fakeStore = {
      getState: () => mockState.ads.ads,

      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, fc.getAd, {
      payload: {
        id: 1,
      },
    }).done;

    expect(api.getAdDetailsById.mock.calls.length).toBe(1);

    expect(dispatchedActions).toContainEqual(
      setAdError(error.response.data.message)
    );
  });
});
