import { runSaga } from "redux-saga";
import * as api from "../../request/processesReguest";
import { render } from "@testing-library/react";
import * as redux from "react-redux";
import SelectionProcessPage from "../../pages/selectionProcessPage/selectionProcessPage";
("../../pages/SelectionProcessPage/SelectionProcessPage");
import store from "../../store";
import "../../i18n";
import { mockState } from "../../mockState";
import { FETCH_PROCESSES_REQ } from "../../store/actions/processes/processesActionConstants";
import { Router } from "react-router-dom";
import history from "../../store/utils/history";
import {
  getProcesses,
  getFilteredProcesses,
  finishProcess,
  changeStatus,
  changeInterviewer,
  getApplicantProcesses,
} from "../../store/saga/processSaga";
import {
  setProcesses,
  setProcessesError,
} from "../../store/actions/processes/processesAction";
import { SelectionProvider } from "../../context/SelectionContext";
import {
  setDoneProcess,
  setDoneProcessError,
  setUpdateInterviewerErr,
  setUpdateInterviewerSucc,
  setUpdateStatusErr,
  setUpdateStatusSucc,
} from "../../store/actions/processes/processAction";
import {
  setApplicant,
  setApplicantError,
} from "../../store/actions/processes/applicantAction";

describe("SelectionProcessPage render tests", () => {
  const cont = (
    <redux.Provider store={store}>
      <SelectionProvider>
        <Router history={history}>
          <SelectionProcessPage />
        </Router>
      </SelectionProvider>
    </redux.Provider>
  );

  let spyOnUseSelector;
  let spyOnUseDispatch;
  let mockDispatch;

  beforeEach(() => {
    // Mock useSelector hook
    spyOnUseSelector = jest.spyOn(redux, "useSelector");
    spyOnUseSelector
      .mockReturnValueOnce(mockState.selections)
      .mockReturnValueOnce(mockState.selections.processes)
      .mockReturnValueOnce(mockState.selections.statuses);
    // Mock useDispatch hook
    spyOnUseDispatch = jest.spyOn(redux, "useDispatch");

    // Mock dispatch function returned from useDispatch
    mockDispatch = jest.fn();
    spyOnUseDispatch.mockReturnValue(mockDispatch);
  });

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

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

  it("should load and handle levels with processes in case of success", async () => {
    // we push all dispatched actions to make assertions easier
    // and our tests less brittle
    const dispatchedActions = [];

    // we don't want to perform an actual api call in our tests
    // so we will mock the getAllUsers api with jest
    // this will mutate the dependency which we may reset if other tests
    // are dependent on it
    const mockedCall = { data: mockState.selections.processes };
    api.getAllLevels = jest.fn(() => Promise.resolve(mockedCall));

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

    // wait for saga to complete
    await runSaga(fakeStore, getProcesses).done;
    expect(api.getAllLevels.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setProcesses(mockedCall.data));
  });

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

    // we simulate an error by rejecting the promise
    // then we assert if our saga dispatched the action(s) correctly
    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };
    api.getAllLevels = jest.fn(() => Promise.reject(error));

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

    await runSaga(fakeStore, getProcesses).done;

    expect(api.getAllLevels.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(
      setProcessesError(error.response.data.message)
    );
  });

  it("should load and handle levels with filtered processes in case of success", async () => {
    // we push all dispatched actions to make assertions easier
    // and our tests less brittle
    const dispatchedActions = [];
    const filter = {
      statuses: ["Zakazan", "Odrađen"],
      dateStart: new Date(2023, 0, 5),
      dateEnd: new Date(2024, 1, 1),
    };
    const filteredData = [];
    mockState.selections.processes.forEach((level) => {
      const filteredLevel = level;
      filteredLevel.selectionProcesses = level.selectionProcesses.filter(
        (v) =>
          v.date >= filter.dateStart &&
          v.date <= filter.dateEnd &&
          filter.statuses.includes(v.status)
      );
      filteredData.push(filteredLevel);
    });
    // we don't want to perform an actual api call in our tests
    // so we will mock the getAllUsers api with jest
    // this will mutate the dependency which we may reset if other tests
    // are dependent on it
    const mockedCall = { data: filteredData };
    api.getAllFilteredProcessesReq = jest.fn(() => Promise.resolve(mockedCall));

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

    // wait for saga to complete
    await runSaga(fakeStore, getFilteredProcesses, filter).done;
    expect(api.getAllFilteredProcessesReq.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setProcesses(filteredData));
  });

  it("should handle error in case of exception", async () => {
    const dispatchedActions = [];

    const filter = {
      statuses: ["Zakazan", "Odrađen"],
      dateStart: new Date(2023, 0, 5),
      dateEnd: new Date(2024, 1, 1),
    };

    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };
    api.getAllFilteredProcessesReq = jest.fn(() => Promise.reject(error));

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

    // wait for saga to complete
    await runSaga(fakeStore, getFilteredProcesses, filter).done;
    expect(dispatchedActions).toContainEqual(
      setProcessesError(error.response.data.message)
    );
  });

  it("should handle process to set it done in case of success", async () => {
    // we push all dispatched actions to make assertions easier
    // and our tests less brittle
    const dispatchedActions = [];
    const filter = {
      statuses: ["Zakazan", "Odrađen"],
      dateStart: new Date(2023, 0, 5),
      dateEnd: new Date(2024, 1, 1),
    };
    const filteredData = [];
    mockState.selections.processes.forEach((level) => {
      const filteredLevel = level;
      filteredLevel.selectionProcesses = level.selectionProcesses.filter(
        (v) =>
          v.date >= filter.dateStart &&
          v.date <= filter.dateEnd &&
          filter.statuses.includes(v.status)
      );
      filteredData.push(filteredLevel);
    });
    // we don't want to perform an actual api call in our tests
    // so we will mock the getAllUsers api with jest
    // this will mutate the dependency which we may reset if other tests
    // are dependent on it
    const mockedCall = { data: filteredData };
    api.getAllFilteredProcessesReq = jest.fn(() => Promise.resolve(mockedCall));

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

    // wait for saga to complete
    await runSaga(fakeStore, getFilteredProcesses, filter).done;
    expect(api.getAllFilteredProcessesReq.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setProcesses(filteredData));
  });

  it("should handle process to set it done in case of finish success", async () => {
    const dispatchedActions = [];
    const mockedCall = { data: { isSuccess: true } };
    api.doneProcess = jest.fn(() => Promise.resolve(mockedCall));

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

    // wait for saga to complete
    await runSaga(fakeStore, finishProcess, {
      payload: {
        id: 1,
        name: "Some random name",
        applicantId: 5,
        schedulerId: 10,
      },
    }).done;

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

  it("should handle error in case of finish process exception", async () => {
    const dispatchedActions = [];

    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };
    api.doneProcess = jest.fn(() => Promise.reject(error));

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

    // wait for saga to complete
    await runSaga(fakeStore, finishProcess, {
      payload: {
        id: 1,
        name: "Some random name",
        applicantId: 5,
        schedulerId: 10,
      },
    }).done;

    expect(api.doneProcess.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(
      setDoneProcessError(error.response.data.message)
    );
  });

  it("should handle process status update", async () => {
    const dispatchedActions = [];
    const mockedCall = { data: { isSuccess: true } };
    api.updateStatus = jest.fn(() => Promise.resolve(mockedCall));

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

    // wait for saga to complete
    await runSaga(fakeStore, changeStatus, {
      payload: {
        data: {
          schedulerId: 3,
          appointment: "22-10-2023",
          newStatus: "Odrađen",
          processId: 1,
        },
      },
    }).done;

    expect(api.updateStatus.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setUpdateStatusSucc());
  });

  it("should call responsehandler while handling process status update", async () => {
    const dispatchedActions = [];
    const mockedCall = { data: { isSuccess: true } };
    api.updateStatus = jest.fn(() => Promise.resolve(mockedCall));

    const mockfn = jest.fn();

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

    // wait for saga to complete
    await runSaga(fakeStore, changeStatus, {
      payload: {
        data: {
          schedulerId: 3,
          appointment: "22-10-2023",
          newStatus: "Odrađen",
          processId: 1,
        },
        responseHandler: mockfn,
      },
    }).done;

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

  it("should handle process status update error if exception is thrown", async () => {
    const dispatchedActions = [];

    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };

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

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

    // wait for saga to complete
    await runSaga(fakeStore, changeStatus, {
      payload: {
        data: {
          schedulerId: 3,
          appointment: "22-10-2023",
          newStatus: "Odrađen",
          processId: 1,
        },
      },
    }).done;

    expect(api.updateStatus.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(
      setUpdateStatusErr(error.response.data.message)
    );
  });

  it("should not call responseHandler if exception is thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };
    api.updateStatus = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

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

    // wait for saga to complete
    await runSaga(fakeStore, changeStatus, {
      payload: {
        data: {
          schedulerId: 3,
          appointment: "22-10-2023",
          newStatus: "Odrađen",
          processId: 1,
        },
        responseHandler: mockfn,
      },
    }).done;

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

  it("should handle interviewer update", async () => {
    const dispatchedActions = [];
    const mockedCall = { data: { isSuccess: true } };
    api.updateInterviewer = jest.fn(() => Promise.resolve(mockedCall));

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

    // wait for saga to complete
    await runSaga(fakeStore, changeInterviewer, {
      payload: {
        data: {
          schedulerId: 1,
          processId: 2,
        },
        // responseHandler: apiSuccess,
      },
    }).done;

    expect(api.updateInterviewer.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(setUpdateInterviewerSucc());
  });

  it("should call response handler on change success", async () => {
    const dispatchedActions = [];
    const mockedCall = { data: { isSuccess: true } };
    api.updateInterviewer = jest.fn(() => Promise.resolve(mockedCall));

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

    const mockfn = jest.fn();

    // wait for saga to complete
    await runSaga(fakeStore, changeInterviewer, {
      payload: {
        data: {
          schedulerId: 1,
          processId: 2,
        },
        responseHandler: mockfn,
      },
    }).done;

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

  it("should handle interviewer update error if exception is thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };
    api.updateInterviewer = jest.fn(() => Promise.reject(error));

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

    // wait for saga to complete
    await runSaga(fakeStore, changeInterviewer, {
      payload: {
        data: {
          schedulerId: 1,
          processId: 2,
        },
        // responseHandler: mockfn,
      },
    }).done;

    expect(api.updateInterviewer.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(
      setUpdateInterviewerErr(error.response.data.message)
    );
  });

  it("should not call response handler if exception is thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };
    api.updateInterviewer = jest.fn(() => Promise.reject(error));

    const mockfn = jest.fn();

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

    // wait for saga to complete
    await runSaga(fakeStore, changeInterviewer, {
      payload: {
        data: {
          schedulerId: 1,
          processId: 2,
        },
        responseHandler: mockfn,
      },
    }).done;

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

  it("should handle applicant processess if succeeded", async () => {
    const dispatchedActions = [];
    const mockedCall = { data: {} };
    api.getProcessesOfApplicant = jest.fn(() => Promise.resolve(mockedCall));

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

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

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

  it("should handle applicant processess error if exception is thrown", async () => {
    const dispatchedActions = [];
    const error = {
      response: {
        data: { message: mockState.selections.fetchSelectionsErrorMessage },
      },
    };
    api.getProcessesOfApplicant = jest.fn(() => Promise.reject(error));

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

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

    expect(api.getProcessesOfApplicant.mock.calls.length).toBe(1);
    expect(dispatchedActions).toContainEqual(
      setApplicantError(error.response.data.message)
    );
  });
});
