/*
 * MIT License
 *
 * Copyright (c) 2020-present Cloudogu GmbH and Contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import reducer, {
  CREATE_PERMISSION,
  CREATE_PERMISSION_FAILURE,
  CREATE_PERMISSION_PENDING,
  CREATE_PERMISSION_SUCCESS,
  createPermission,
  createPermissionSuccess,
  DELETE_PERMISSION,
  DELETE_PERMISSION_FAILURE,
  DELETE_PERMISSION_PENDING,
  DELETE_PERMISSION_SUCCESS,
  deletePermission,
  deletePermissionSuccess,
  FETCH_PERMISSIONS,
  FETCH_PERMISSIONS_FAILURE,
  FETCH_PERMISSIONS_PENDING,
  FETCH_PERMISSIONS_SUCCESS,
  fetchPermissions,
  fetchPermissionsSuccess,
  getCreatePermissionFailure,
  getDeletePermissionFailure,
  getDeletePermissionsFailure,
  getFetchPermissionsFailure,
  getModifyPermissionFailure,
  getModifyPermissionsFailure,
  getPermissionsOfRepo,
  hasCreatePermission,
  isCreatePermissionPending,
  isDeletePermissionPending,
  isFetchPermissionsPending,
  isModifyPermissionPending,
  MODIFY_PERMISSION,
  MODIFY_PERMISSION_FAILURE,
  MODIFY_PERMISSION_PENDING,
  MODIFY_PERMISSION_SUCCESS,
  modifyPermission,
  modifyPermissionSuccess
} from "./permissions";
import { Permission, PermissionCollection } from "@scm-manager/ui-types";

const hitchhiker_puzzle42Permission_user_eins: Permission = {
  name: "user_eins",
  type: "READ",
  groupPermission: false,
  _links: {
    self: {
      href: "http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
    },
    delete: {
      href: "http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
    },
    update: {
      href: "http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
    }
  },
  verbs: []
};

const hitchhiker_puzzle42Permission_user_zwei: Permission = {
  name: "user_zwei",
  type: "WRITE",
  groupPermission: true,
  _links: {
    self: {
      href: "http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
    },
    delete: {
      href: "http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
    },
    update: {
      href: "http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
    }
  },
  verbs: []
};

const hitchhiker_puzzle42Permissions: PermissionCollection = [
  hitchhiker_puzzle42Permission_user_eins,
  hitchhiker_puzzle42Permission_user_zwei
];

const hitchhiker_puzzle42RepoPermissions = {
  _embedded: {
    permissions: hitchhiker_puzzle42Permissions
  },
  _links: {
    create: {
      href: "http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions"
    }
  }
};

describe("permission fetch", () => {
  const REPOS_URL = "/api/v2/repositories";
  const URL = "repositories";
  const mockStore = configureMockStore([thunk]);

  afterEach(() => {
    fetchMock.reset();
    fetchMock.restore();
  });

  it("should successfully fetch permissions to repo hitchhiker/puzzle42", () => {
    fetchMock.getOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", hitchhiker_puzzle42RepoPermissions);

    const expectedActions = [
      {
        type: FETCH_PERMISSIONS_PENDING,
        payload: {
          namespace: "hitchhiker",
          repoName: "puzzle42"
        },
        itemId: "hitchhiker/puzzle42"
      },
      {
        type: FETCH_PERMISSIONS_SUCCESS,
        payload: hitchhiker_puzzle42RepoPermissions,
        itemId: "hitchhiker/puzzle42"
      }
    ];

    const store = mockStore({});
    return store
      .dispatch(fetchPermissions(URL + "/hitchhiker/puzzle42/permissions", "hitchhiker", "puzzle42"))
      .then(() => {
        expect(store.getActions()).toEqual(expectedActions);
      });
  });

  it("should dispatch FETCH_PERMISSIONS_FAILURE, it the request fails", () => {
    fetchMock.getOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
      status: 500
    });

    const store = mockStore({});
    return store
      .dispatch(fetchPermissions(URL + "/hitchhiker/puzzle42/permissions", "hitchhiker", "puzzle42"))
      .then(() => {
        const actions = store.getActions();
        expect(actions[0].type).toEqual(FETCH_PERMISSIONS_PENDING);
        expect(actions[1].type).toEqual(FETCH_PERMISSIONS_FAILURE);
        expect(actions[1].payload).toBeDefined();
      });
  });

  it("should successfully modify user_eins permission", () => {
    fetchMock.putOnce(hitchhiker_puzzle42Permission_user_eins._links.update.href, {
      status: 204
    });

    const editedPermission = {
      ...hitchhiker_puzzle42Permission_user_eins,
      type: "OWNER"
    };

    const store = mockStore({});

    return store.dispatch(modifyPermission(editedPermission, "hitchhiker", "puzzle42")).then(() => {
      const actions = store.getActions();
      expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
      expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
    });
  });

  it("should successfully modify user_eins permission and call the callback", () => {
    fetchMock.putOnce(hitchhiker_puzzle42Permission_user_eins._links.update.href, {
      status: 204
    });

    const editedPermission = {
      ...hitchhiker_puzzle42Permission_user_eins,
      type: "OWNER"
    };

    const store = mockStore({});

    let called = false;
    const callback = () => {
      called = true;
    };

    return store.dispatch(modifyPermission(editedPermission, "hitchhiker", "puzzle42", callback)).then(() => {
      const actions = store.getActions();
      expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
      expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
      expect(called).toBe(true);
    });
  });

  it("should fail modifying on HTTP 500", () => {
    fetchMock.putOnce(hitchhiker_puzzle42Permission_user_eins._links.update.href, {
      status: 500
    });

    const editedPermission = {
      ...hitchhiker_puzzle42Permission_user_eins,
      type: "OWNER"
    };

    const store = mockStore({});

    return store.dispatch(modifyPermission(editedPermission, "hitchhiker", "puzzle42")).then(() => {
      const actions = store.getActions();
      expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
      expect(actions[1].type).toEqual(MODIFY_PERMISSION_FAILURE);
      expect(actions[1].payload).toBeDefined();
    });
  });

  it("should add a permission successfully", () => {
    // unmatched
    fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
      status: 204,
      headers: {
        location: "repositories/hitchhiker/puzzle42/permissions/user_eins"
      }
    });

    fetchMock.getOnce(
      REPOS_URL + "/hitchhiker/puzzle42/permissions/user_eins",
      hitchhiker_puzzle42Permission_user_eins
    );

    const store = mockStore({});
    return store
      .dispatch(
        createPermission(
          URL + "/hitchhiker/puzzle42/permissions",
          hitchhiker_puzzle42Permission_user_eins,
          "hitchhiker",
          "puzzle42"
        )
      )
      .then(() => {
        const actions = store.getActions();
        expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
        expect(actions[1].type).toEqual(CREATE_PERMISSION_SUCCESS);
      });
  });

  it("should fail adding a permission on HTTP 500", () => {
    fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
      status: 500
    });

    const store = mockStore({});
    return store
      .dispatch(
        createPermission(
          URL + "/hitchhiker/puzzle42/permissions",
          hitchhiker_puzzle42Permission_user_eins,
          "hitchhiker",
          "puzzle42"
        )
      )
      .then(() => {
        const actions = store.getActions();
        expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
        expect(actions[1].type).toEqual(CREATE_PERMISSION_FAILURE);
        expect(actions[1].payload).toBeDefined();
      });
  });

  it("should call the callback after permission successfully created", () => {
    // unmatched
    fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
      status: 204,
      headers: {
        location: "repositories/hitchhiker/puzzle42/permissions/user_eins"
      }
    });

    fetchMock.getOnce(
      REPOS_URL + "/hitchhiker/puzzle42/permissions/user_eins",
      hitchhiker_puzzle42Permission_user_eins
    );
    let callMe = "not yet";

    const callback = () => {
      callMe = "yeah";
    };

    const store = mockStore({});
    return store
      .dispatch(
        createPermission(
          URL + "/hitchhiker/puzzle42/permissions",
          hitchhiker_puzzle42Permission_user_eins,
          "hitchhiker",
          "puzzle42",
          callback
        )
      )
      .then(() => {
        expect(callMe).toBe("yeah");
      });
  });
  it("should delete successfully permission user_eins", () => {
    fetchMock.deleteOnce(hitchhiker_puzzle42Permission_user_eins._links.delete.href, {
      status: 204
    });

    const store = mockStore({});
    return store
      .dispatch(deletePermission(hitchhiker_puzzle42Permission_user_eins, "hitchhiker", "puzzle42"))
      .then(() => {
        const actions = store.getActions();
        expect(actions.length).toBe(2);
        expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
        expect(actions[0].payload).toBe(hitchhiker_puzzle42Permission_user_eins);
        expect(actions[1].type).toEqual(DELETE_PERMISSION_SUCCESS);
      });
  });

  it("should call the callback, after successful delete", () => {
    fetchMock.deleteOnce(hitchhiker_puzzle42Permission_user_eins._links.delete.href, {
      status: 204
    });

    let called = false;
    const callMe = () => {
      called = true;
    };

    const store = mockStore({});
    return store
      .dispatch(deletePermission(hitchhiker_puzzle42Permission_user_eins, "hitchhiker", "puzzle42", callMe))
      .then(() => {
        expect(called).toBeTruthy();
      });
  });

  it("should fail to delete permission", () => {
    fetchMock.deleteOnce(hitchhiker_puzzle42Permission_user_eins._links.delete.href, {
      status: 500
    });

    const store = mockStore({});
    return store
      .dispatch(deletePermission(hitchhiker_puzzle42Permission_user_eins, "hitchhiker", "puzzle42"))
      .then(() => {
        const actions = store.getActions();
        expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
        expect(actions[0].payload).toBe(hitchhiker_puzzle42Permission_user_eins);
        expect(actions[1].type).toEqual(DELETE_PERMISSION_FAILURE);
        expect(actions[1].payload).toBeDefined();
      });
  });
});

describe("permissions reducer", () => {
  it("should return empty object, if state and action is undefined", () => {
    expect(reducer()).toEqual({});
  });

  it("should return the same state, if the action is undefined", () => {
    const state = {
      x: true
    };
    expect(reducer(state)).toBe(state);
  });

  it("should return the same state, if the action is unknown to the reducer", () => {
    const state = {
      x: true
    };
    expect(
      reducer(state, {
        type: "EL_SPECIALE"
      })
    ).toBe(state);
  });

  it("should store the permissions on FETCH_PERMISSION_SUCCESS", () => {
    const newState = reducer({}, fetchPermissionsSuccess(hitchhiker_puzzle42RepoPermissions, "hitchhiker", "puzzle42"));

    expect(newState["hitchhiker/puzzle42"].entries).toBe(hitchhiker_puzzle42Permissions);
  });

  it("should update permission", () => {
    const oldState = {
      "hitchhiker/puzzle42": {
        entries: [hitchhiker_puzzle42Permission_user_eins]
      }
    };
    const permissionEdited = {
      ...hitchhiker_puzzle42Permission_user_eins,
      type: "OWNER"
    };
    const expectedState = {
      "hitchhiker/puzzle42": {
        entries: [permissionEdited]
      }
    };
    const newState = reducer(oldState, modifyPermissionSuccess(permissionEdited, "hitchhiker", "puzzle42"));
    expect(newState["hitchhiker/puzzle42"]).toEqual(expectedState["hitchhiker/puzzle42"]);
  });

  it("should remove permission from state when delete succeeds", () => {
    const state = {
      "hitchhiker/puzzle42": {
        entries: [hitchhiker_puzzle42Permission_user_eins, hitchhiker_puzzle42Permission_user_zwei]
      }
    };

    const expectedState = {
      "hitchhiker/puzzle42": {
        entries: [hitchhiker_puzzle42Permission_user_zwei]
      }
    };

    const newState = reducer(
      state,
      deletePermissionSuccess(hitchhiker_puzzle42Permission_user_eins, "hitchhiker", "puzzle42")
    );
    expect(newState["hitchhiker/puzzle42"]).toEqual(expectedState["hitchhiker/puzzle42"]);
  });

  it("should add permission", () => {
    //changing state had to be removed because of errors
    const oldState = {
      "hitchhiker/puzzle42": {
        entries: [hitchhiker_puzzle42Permission_user_eins]
      }
    };
    const expectedState = {
      "hitchhiker/puzzle42": {
        entries: [hitchhiker_puzzle42Permission_user_eins, hitchhiker_puzzle42Permission_user_zwei]
      }
    };
    const newState = reducer(
      oldState,
      createPermissionSuccess(hitchhiker_puzzle42Permission_user_zwei, "hitchhiker", "puzzle42")
    );
    expect(newState["hitchhiker/puzzle42"]).toEqual(expectedState["hitchhiker/puzzle42"]);
  });
});

describe("permissions selectors", () => {
  const error = new Error("something goes wrong");

  it("should return the permissions of one repository", () => {
    const state = {
      permissions: {
        "hitchhiker/puzzle42": {
          entries: hitchhiker_puzzle42Permissions
        }
      }
    };

    const repoPermissions = getPermissionsOfRepo(state, "hitchhiker", "puzzle42");
    expect(repoPermissions).toEqual(hitchhiker_puzzle42Permissions);
  });

  it("should return true, when fetch permissions is pending", () => {
    const state = {
      pending: {
        [FETCH_PERMISSIONS + "/hitchhiker/puzzle42"]: true
      }
    };
    expect(isFetchPermissionsPending(state, "hitchhiker", "puzzle42")).toEqual(true);
  });

  it("should return false, when fetch permissions is not pending", () => {
    expect(isFetchPermissionsPending({}, "hitchiker", "puzzle42")).toEqual(false);
  });

  it("should return error when fetch permissions did fail", () => {
    const state = {
      failure: {
        [FETCH_PERMISSIONS + "/hitchhiker/puzzle42"]: error
      }
    };
    expect(getFetchPermissionsFailure(state, "hitchhiker", "puzzle42")).toEqual(error);
  });

  it("should return undefined when fetch permissions did not fail", () => {
    expect(getFetchPermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(undefined);
  });

  it("should return true, when modify permission is pending", () => {
    const state = {
      pending: {
        [MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: true
      }
    };
    expect(isModifyPermissionPending(state, "hitchhiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)).toEqual(
      true
    );
  });

  it("should return false, when modify permission is not pending", () => {
    expect(isModifyPermissionPending({}, "hitchiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)).toEqual(
      false
    );
  });

  it("should return error when modify permission did fail", () => {
    const state = {
      failure: {
        [MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
      }
    };
    expect(
      getModifyPermissionFailure(state, "hitchhiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)
    ).toEqual(error);
  });

  it("should return undefined when modify permission did not fail", () => {
    expect(getModifyPermissionFailure({}, "hitchhiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)).toBe(
      undefined
    );
  });

  it("should return error when one of the modify permissions did fail", () => {
    const state = {
      permissions: {
        "hitchhiker/puzzle42": {
          entries: hitchhiker_puzzle42Permissions
        }
      },
      failure: {
        [MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
      }
    };
    expect(getModifyPermissionsFailure(state, "hitchhiker", "puzzle42")).toEqual(error);
  });

  it("should return undefined when no modify permissions did not fail", () => {
    expect(getModifyPermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(undefined);
  });

  it("should return true, when createPermission is true", () => {
    const state = {
      permissions: {
        "hitchhiker/puzzle42": {
          createPermission: true
        }
      }
    };
    expect(hasCreatePermission(state, "hitchhiker", "puzzle42")).toBe(true);
  });

  it("should return false, when createPermission is false", () => {
    const state = {
      permissions: {
        "hitchhiker/puzzle42": {
          createPermission: false
        }
      }
    };
    expect(hasCreatePermission(state, "hitchhiker", "puzzle42")).toEqual(false);
  });

  it("should return true, when delete permission is pending", () => {
    const state = {
      pending: {
        [DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: true
      }
    };
    expect(isDeletePermissionPending(state, "hitchhiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)).toEqual(
      true
    );
  });

  it("should return false, when delete permission is not pending", () => {
    expect(isDeletePermissionPending({}, "hitchiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)).toEqual(
      false
    );
  });

  it("should return error when delete permission did fail", () => {
    const state = {
      failure: {
        [DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
      }
    };
    expect(
      getDeletePermissionFailure(state, "hitchhiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)
    ).toEqual(error);
  });

  it("should return undefined when delete permission did not fail", () => {
    expect(getDeletePermissionFailure({}, "hitchhiker", "puzzle42", hitchhiker_puzzle42Permission_user_eins)).toBe(
      undefined
    );
  });

  it("should return error when one of the delete permissions did fail", () => {
    const state = {
      permissions: {
        "hitchhiker/puzzle42": {
          entries: hitchhiker_puzzle42Permissions
        }
      },
      failure: {
        [DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
      }
    };
    expect(getDeletePermissionsFailure(state, "hitchhiker", "puzzle42")).toEqual(error);
  });

  it("should return undefined when no delete permissions did not fail", () => {
    expect(getDeletePermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(undefined);
  });

  it("should return true, when create permission is pending", () => {
    const state = {
      pending: {
        [CREATE_PERMISSION + "/hitchhiker/puzzle42"]: true
      }
    };
    expect(isCreatePermissionPending(state, "hitchhiker", "puzzle42")).toEqual(true);
  });

  it("should return false, when create permissions is not pending", () => {
    expect(isCreatePermissionPending({}, "hitchiker", "puzzle42")).toEqual(false);
  });

  it("should return error when create permissions did fail", () => {
    const state = {
      failure: {
        [CREATE_PERMISSION + "/hitchhiker/puzzle42"]: error
      }
    };
    expect(getCreatePermissionFailure(state, "hitchhiker", "puzzle42")).toEqual(error);
  });

  it("should return undefined when create permissions did not fail", () => {
    expect(getCreatePermissionFailure({}, "hitchhiker", "puzzle42")).toBe(undefined);
  });
});
