import { ofType } from 'redux-observable';
import { concat, EMPTY, merge, of, Subject } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { catchError, concatMap, map, switchMap } from 'rxjs/operators';

import { getCurrentUploadStateFromQueue, processedUploadQueueIdSelector } from '../../selectors';
import {
  createVideoAction,
  errorAction,
  loadingAction,
  loadingProgressAction,
  setUploadVideoCcFilesAction,
  uploadCcFilesAction,
} from '../../slices';
import { requestEventAction, responseEventAction } from '@/modules/@common/request/redux/slices';

export default (action$, state$) =>
  action$.pipe(
    ofType(uploadCcFilesAction.type),
    switchMap(() => {
      const processedUploadQueueId = processedUploadQueueIdSelector(state$.value);
      const { ccFiles, uploadVideo } = getCurrentUploadStateFromQueue(state$.value);

      if (uploadVideo.ccFiles?.length) {
        const upload = [];
        const totalSize = ccFiles.reduce((acc, value) => acc + value.fileObject.size, 0);
        let loadedStorage = 0;
        const makeUploadStream = ({ file, uploadUrl }) => {
          const progressSubscriber = new Subject();
          const request = ajax({
            method: 'PUT',
            url: uploadUrl,
            headers: {
              'Content-Type': 'application/octet-stream',
            },
            crossDomain: true,
            withCredentials: true,
            body: file,
            progressSubscriber,
          });

          const requestObservable = request.pipe(
            concatMap(() => {
              const state = getCurrentUploadStateFromQueue(state$.value);
              const ccFilesWithUploadState = state.uploadVideo.ccFiles.map((ccFile) => ({
                ...ccFile,
                isUploaded: ccFile.uploadUrl === uploadUrl || ccFile.isUploaded,
              }));
              return of(
                setUploadVideoCcFilesAction({
                  ccFiles: ccFilesWithUploadState,
                  processedUploadQueueId,
                }),
              );
            }),
          );

          const stream$ = merge(
            progressSubscriber.pipe(
              map((e) => {
                loadedStorage += e.loaded;

                const { video } = getCurrentUploadStateFromQueue(state$.value);
                const loaded = video ? video.size + loadedStorage : loadedStorage;
                const total = video ? video.size + totalSize : totalSize;

                return { percentage: loaded / total };
              }),
              map((data) =>
                loadingProgressAction({
                  percentage: data.percentage,
                  processedUploadQueueId,
                }),
              ),
            ),
            requestObservable,
          ).pipe(
            catchError(() => {
              const errorActions = [
                of(
                  errorAction({
                    reason: 'error',
                    message: 'Network connection error',
                  }),
                ),
                of(loadingAction()),
              ];

              return concat(...errorActions);
            }),
          );

          return concat(of(requestEventAction()), stream$, of(responseEventAction()));
        };
        uploadVideo.ccFiles.forEach((ccFile, index) => {
          const { uploadUrl } = ccFile;
          const { fileObject } = ccFiles[index];

          upload.push(
            makeUploadStream({
              file: fileObject,
              uploadUrl,
            }),
          );
        });

        const uploadStream = merge(...upload).pipe(
          switchMap((action) => {
            const state = getCurrentUploadStateFromQueue(state$.value);
            const shouldCreateVideo = state.uploadVideo.ccFiles.every((ccFile) => ccFile?.isUploaded);

            if (shouldCreateVideo) {
              return of(createVideoAction());
            }

            if (action?.type) {
              return of(action);
            }

            return EMPTY;
          }),
        );

        return concat(uploadStream);
      }

      return of(createVideoAction());
    }),
  );
