import React from 'react';
import { Api, ApiError } from 'backend-api';
import { isArtists } from 'backend-api/guards';
import { Artist } from 'backend-api/models';
import { Observable, of } from 'rxjs';
import { store } from 'app';
import { StateObservable } from 'redux-observable';
import { asyncEpic, ofType } from 'core/epics';
import { replaceTo } from 'utils/navigation';
import { resetAccessRestrictedData, setAccessRestrictedData } from 'common/actions';
import { PROJECT_ACCESS_RESTRICTED_CODES } from 'common-v2/constants';
import { RestrictionCode } from 'common-v2/types';
import { getHigestRole, getRoleNameById, getValuesDiff } from 'common-v2/transducers';
import {
  editProject,
  getUsers,
  getProjectDetails,
  getProjectSharingInfo,
  searchArtists,
  updateProjectUser,
  addProjectUsers,
  removeProjectUser,
} from './actions';
import { increaseUserCounter, UserCounters, trackEvent, AnalyticsEvents } from 'utils/analytic';
import { isSharingInfoEmpty, prepareRequest } from './transducers';
import { catchError, switchMap, tap, mergeMap } from 'rxjs/operators';
import { ProjectState } from './reducer';

export const getProjectDetailsEpic = asyncEpic(
  getProjectDetails,
  ({ payload }) => Api.getProject(payload.projectId, payload.params),
  (error, { projectId }) => {
    if (error.statusCode === 403) {
      const errorDetail = error.data?.detail;
      const errorCode = error.data?.code;

      if (errorDetail && PROJECT_ACCESS_RESTRICTED_CODES.some(val => val === errorCode)) {
        store.dispatch(
          setAccessRestrictedData({
            currentTeamMembers: errorDetail.projectEditors,
            currentProjectId: projectId,
            currentProjectName: errorDetail.projectName,
          })
        );
      } else {
        store.dispatch(resetAccessRestrictedData());
      }

      replaceTo(`/projects/${projectId}/access-restricted`);
    }

    return of(getProjectDetails.failure(error));
  },
  {
    showError: false,
  }
);

export const getProjectSharingInfoEpic = asyncEpic(
  getProjectSharingInfo,
  ({ payload }) => Api.getProjectSharingInfo(payload),
  (error: ApiError, projectId) => {
    if (error.statusCode === 403) {
      replaceTo(`/projects/${projectId}/access-restricted`);
    }

    return of(getProjectSharingInfo.failure(error));
  },
  { showError: false }
);

export const editProjectEpic = (action$: Observable<any>) =>
  action$.pipe(
    ofType(editProject.request),
    switchMap(({ payload: { project, data, changedFields, onFinished, onSuccess, onError } }) => {
      const params = prepareRequest(data);

      if (project.isClaimed) {
        if (changedFields?.length === 0) {
          onFinished?.();
          return of(editProject.success(project));
        }

        return Api.editProject(project.id, params).pipe(
          tap(() => increaseUserCounter(UserCounters.PROJECTS_EDITED, 1)),
          tap(() => {
            trackEvent(AnalyticsEvents.PROJECT_EDITED, {
              project_id: project.id,
              project_fields_edited: changedFields,
            });
          }),
          tap(() => {
            const targets = project.targets.items;
            if (!changedFields?.some(field => field === 'artists') || !isArtists(targets)) return;

            const artistsDiff = getValuesDiff<Artist>(targets, data.artists);

            artistsDiff.added.forEach(artist => {
              trackEvent(AnalyticsEvents.FEATURED_ARTIST_ADDED, {
                project_id: project.id,
                artist_id: artist.id,
                label: project.label?.name,
                current_user_role: getHigestRole(project.userRoles),
              });
            });

            artistsDiff.removed.forEach(artist => {
              trackEvent(AnalyticsEvents.FEATURED_ARTIST_REMOVED, {
                project_id: project.id,
                artist_id: artist.id,
                label: project.label?.name,
                current_user_role: getHigestRole(project.userRoles)?.name,
              });
            });
          }),
          tap(() => {
            onSuccess?.('Project details have been updated.');
          }),
          mergeMap(project => [editProject.success(project)]),
          catchError(error => {
            onError?.(error);

            return of(editProject.failure(error));
          })
        );
      }

      return Api.claimUnassignedProject(project.id, params).pipe(
        tap(() => {
          onSuccess(
            <>
              The project has been successfully <b>claimed.</b> You can start working on the project now!
            </>
          );
        }),
        mergeMap(project => [editProject.success(project)]),
        catchError(error => {
          onError(error);

          return of(editProject.failure(error));
        })
      );
    })
  );

export const searchArtistEpic = asyncEpic(
  searchArtists,
  ({ payload: { name } }) => Api.searchArtist(name),
  (error: ApiError, { onFailure }) => {
    onFailure();
    return of(searchArtists.failure(error));
  },
  { showError: false }
);

export const getUsersEpic = asyncEpic(getUsers, () => Api.getUsers());

export const addProjectUsersEpic = (action$: Observable<any>) =>
  action$.pipe(
    ofType(addProjectUsers.request),
    switchMap(
      ({
        payload: {
          projectId,
          usersIds,
          roleId,
          currenUserRole,
          onSuccess,
          onFailure,
          onEditRestricted,
          onProjectRestricted,
        },
      }) => {
        return Api.addProjectUsers(projectId, { usersIds, roleId }).pipe(
          tap(() => {
            usersIds.forEach(userId => {
              trackEvent(AnalyticsEvents.TEAMMATE_ADDED, {
                project_id: projectId,
                member_id: userId,
                member_role: getRoleNameById(roleId),
                current_user_role: currenUserRole,
              });
            });
          }),
          mergeMap(() => [addProjectUsers.success(), getProjectSharingInfo.request(projectId)]),
          tap(() => onSuccess()),
          catchError(error => {
            if (error.statusCode === 403) {
              switch (error.data?.code) {
                case RestrictionCode.TeamEditRestricted:
                  onEditRestricted(error);
                  break;
                case RestrictionCode.ProjectAccessRestricted:
                case RestrictionCode.UnclaimedProjectAccessRestricted:
                  onProjectRestricted();
                  break;
                default:
                  onFailure();
              }
            } else {
              onFailure();
            }

            return of(addProjectUsers.failure(error));
          })
        );
      }
    )
  );

export const updateProjectUserEpic = (action$: Observable<any>) =>
  action$.pipe(
    ofType(updateProjectUser.request),
    switchMap(
      ({ payload: { projectId, userId, role, previousRole, onFailure, onEditRestricted, onProjectRestricted } }) => {
        return Api.updateProjectUser(projectId, { userId, roleId: role.id }).pipe(
          mergeMap(() => [updateProjectUser.success()]),
          catchError(error => {
            if (error.statusCode === 403) {
              switch (error.data?.code) {
                case RestrictionCode.TeamEditRestricted:
                  onEditRestricted(error);
                  break;
                case RestrictionCode.ProjectAccessRestricted:
                case RestrictionCode.UnclaimedProjectAccessRestricted:
                  onProjectRestricted();
                  break;
                default:
                  onFailure();
              }
            } else {
              onFailure();
            }

            return of(updateProjectUser.failure({ userId, role: previousRole }));
          })
        );
      }
    )
  );

export const removeProjectUserEpic = (action$: Observable<any>, state$: StateObservable<any>) =>
  action$.pipe(
    ofType(removeProjectUser.request),
    switchMap(
      ({
        payload: {
          projectId,
          userId,
          roleId,
          currenUserRole,
          onFailure,
          onEditRestricted,
          onProjectRestricted,
          onProjectUnclaim,
        },
      }) => {
        return Api.removeProjectUser(projectId, { userId, roleId }).pipe(
          tap(() => {
            trackEvent(AnalyticsEvents.TEAMMATE_REMOVED, {
              project_id: projectId,
              member_id: userId,
              member_role: getRoleNameById(roleId),
              current_user_role: currenUserRole,
            });
          }),
          mergeMap(() => {
            const sharingInfo = (state$.value.projectV2 as ProjectState).shareModal.content.data;
            const isProjectUnclaimed = isSharingInfoEmpty(sharingInfo);

            if (isProjectUnclaimed) {
              onProjectUnclaim();

              return [removeProjectUser.success(), getProjectDetails.request({ projectId })];
            }

            return [removeProjectUser.success()];
          }),
          catchError(error => {
            if (error.statusCode === 403) {
              switch (error.data?.code) {
                case RestrictionCode.TeamEditRestricted:
                  onEditRestricted(error);
                  break;
                case RestrictionCode.ProjectAccessRestricted:
                case RestrictionCode.UnclaimedProjectAccessRestricted:
                  onProjectRestricted();
                  break;
                default:
                  onFailure();
              }
            } else {
              onFailure();
            }

            return of(removeProjectUser.failure(error));
          })
        );
      }
    )
  );
