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 PatternsPage from "../../pages/PatternsPage/PatternsPage";
import * as api from "../../request/patternsRequest";
import { runSaga } from "redux-saga";
import { FETCH_PATTERNS_REQ } from "../../store/actions/patterns/patternsActionConstants";
import ColorModeProvider from "../../context/ColorModeContext";
import * as fc from "../../store/saga/patternsSaga";
import {
  setFilteredPatterns,
  setPatterns,
} from "../../store/actions/patterns/patternsActions";
import { setPattern } from "../../store/actions/pattern/patternActions";
import { setPatternApplicants } from "../../store/actions/patternApplicants/patternApplicantsActions";
import { createPattern } from "../../store/actions/createPattern/createPatternActions";
import { updatePattern } from "../../store/actions/updatePattern/updatePatternActions";
import { scheduleAppointment } from "../../store/actions/scheduleAppointment/scheduleAppointmentActions";

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

  let spyOnUseSelector;
  let spyOnUseDispatch;
  let mockDispatch;

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

    spyOnUseDispatch = jest.spyOn(redux, "useDispatch");
    mockDispatch = jest.fn();
    spyOnUseDispatch.mockReturnValue(mockDispatch);
  });

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

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

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

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

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

    await runSaga(fakeStore, fc.getPatterns, {}).done;
    expect(api.getAllPatterns.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setPatterns(mockedCall.data));
  });

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

    const filteredData = mockState.patterns.patterns.filter(
      (pattern) => pattern.id === id
    );

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

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

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

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

    const filteredData = mockState.patterns.patterns.filter(
      (pattern) => pattern.id === id
    );

    const mockedCall = {
      data: {
        ...filteredData[0].selectionLevel.selectionProcesses[0].applicant,
      },
    };
    api.getPatternApplicantsById = jest.fn(() => Promise.resolve(mockedCall));

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

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

  it("Should load and handle filtered patterns in case of success", async () => {
    const dispatchedActions = [];
    const filters = {
      fromDate: new Date("2-2-2021"),
      toDate: new Date("3-3-2023"),
      selectionLevels: [1, 2],
    };

    const filteredData = mockState.patterns.patterns.filter(
      (pattern) =>
        pattern.createdAt >= filters.fromDate &&
        pattern.createdAt <= filters.toDate
    );

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

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

    await runSaga(fakeStore, fc.filterPatterns, filters).done;
    expect(api.getFilteredPatterns.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(
      setFilteredPatterns(mockedCall.data)
    );
  });

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

    const mockedCall = {
      data: {
        title: "Neuspesan korak",
        selectionLevelId: 1,
        message: "Poruka",
      },
    };
    api.createPatternRequest = jest.fn(() => Promise.resolve(mockedCall));

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

    await runSaga(fakeStore, fc.createPatternSaga, {
      payload: {
        title: "Neuspesan korak",
        selectionLevelId: 1,
        message: "Poruka",
      },
    }).done;

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

  it("Should update pattern", async () => {
    const dispatchedActions = [];

    const mockedCall = {
      data: {
        id: 1,
        title: "Zakazan intervju",
        createdAt: new Date(),
        selectionLevelId: 1,
        message: "Message",
      },
    };
    api.updatePatternRequest = jest.fn(() => Promise.resolve(mockedCall));

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

    await runSaga(fakeStore, fc.updatePatternSaga, {
      payload: {
        id: 1,
        title: "Zakazan intervju",
        createdAt: new Date(),
        selectionLevelId: 1,
        message: "Message",
      },
    }).done;

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

  it("Should schedule appointment", async () => {
    const dispatchedActions = [];

    const mockedCall = {
      data: {
        emails: ["ermin.bronja@dilig.net"],
        patternId: 1,
        handleApiResponseSuccess: jest.fn,
      },
    };
    api.scheduleAppointmentRequest = jest.fn(() => Promise.resolve(mockedCall));

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

    await runSaga(fakeStore, fc.scheduleAppointmentSaga, {
      payload: {
        emails: ["ermin.bronja@dilig.net"],
        patternId: 1,
        handleApiResponseSuccess: jest.fn,
      },
    }).done;

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

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

    const mockfn = jest.fn();

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

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

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

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

    const mockfn = jest.fn();

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

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

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

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

    const mockfn = jest.fn();

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

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

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

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

    const mockfn = jest.fn();

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

    // wait for saga to complete
    await runSaga(fakeStore, fc.filterPatterns, {
      fromDate: new Date("2-2-2021"),
      toDate: new Date("3-3-2023"),
      selectionLevels: [1, 2],
    }).done;

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

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

    const mockfn = jest.fn();

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

    // wait for saga to complete
    await runSaga(fakeStore, fc.createPatternSaga, {
      payload: {
        title: "Neuspesan korak",
        selectionLevelId: 1,
        message: "Poruka",
      },
    }).done;

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

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

    const mockfn = jest.fn();

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

    // wait for saga to complete
    await runSaga(fakeStore, fc.updatePatternSaga, {
      payload: {
        id: 1,
        title: "Zakazan intervju",
        createdAt: new Date(),
        selectionLevelId: 1,
        message: "Message",
      },
    }).done;

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

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

    const mockfn = jest.fn();

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

    // wait for saga to complete
    await runSaga(fakeStore, fc.scheduleAppointmentSaga, {
      payload: {
        emails: ["ermin.bronja@dilig.net"],
        patternId: 1,
        handleApiResponseSuccess: jest.fn,
      },
    }).done;

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