import React, { useState, useEffect } from 'react';

import Dropzone from 'react-dropzone';

import isEmpty from 'lodash/isEmpty';

import { BiUpload, BiErrorCircle } from 'react-icons/bi';

import { FaFileAlt, FaTrash } from 'react-icons/fa';
import { MdClear } from 'react-icons/md';
// Redux part
import { useSelector, useDispatch } from 'react-redux';
import { StoreState } from 'store/rootReducer';

// Import actions here

import classNames from 'classnames/bind';
import { getFileName } from 'store/utils/api';

import TextItem from 'components/items/TextItem';
import app from 'store/app';
import FormItemLabel from 'components/items/FormItemLabel';
import Button from 'components/items/Button';
import LibraryImages from 'components/items/LibraryImages/LibraryImages';
import cloneDeep from 'lodash/cloneDeep';
import findIndex from 'lodash/findIndex';
import isEqual from 'lodash/isEqual';

import { copyClipboard, deepOmit } from 'store/utils/helper';
import FormElement from 'components/structure/FormElement';
import fileActions from 'store/file/actions';
import styleIdentifiers from './MultipleUploadZone.module.scss';

const styles = classNames.bind(styleIdentifiers);

export interface StateProps {
  className?: string;
  input: object;
  errorTitle?: string;
  errorMessage?: string;
  noMargin?: boolean;
  label?: string;
  showProgress?: boolean;
  disabled?: boolean;
  maxSize?: number;
  accept?: string;
  small?: boolean;
  meta: object;
  noFileName?: boolean;
  inputs?: object;
  upload: Function;
  uploadPrefix?: string;
  noUnique;
  treatmentFile?: Function;
  saveValue?: string;
  onChange?: Function;
  height?: number;
  noInfo?: boolean;
  info?: string;
  subinfo?: string;
  children?: JSX.Element;
  withLibrary?: boolean;
  renderPreviewTop?: boolean;
  displayName?: string;
  displayValue?: string;
  oneFile?: boolean;
  previewType?: string;
  // Classnames
  dropzoneClassName?: string;
  activeClassName?: string;
  acceptClassName?: string;
  rejectClassName?: string;
  disabledClassName?: string;
  infoClassName?: string;
  subInfoClassName?: string;
  iconDropzoneClassName?: string;
  filePreviewClassName?: string;
  iconClassName?: string;
  progressBarClassName?: string;
  errorIconClassName?: string;
  fileIconClassName?: string;
  // JSX
  iconUpload?: JSX.Element;
  iconFile?: JSX.Element;
  iconError?: JSX.Element;
  iconRemove?: JSX.Element;
  // Component
  fileItem?: React.Component;
  // Upload in one graph request or not
  multipleUpload?: boolean;
  keepId?: boolean;
}

export interface DispatchProps {}

export interface OwnProps {}

export type MultipleUploadZoneProps = StateProps & DispatchProps & OwnProps;

const MultipleUploadZone = (props: MultipleUploadZoneProps) => {
  const {
    className,
    input,
    errorTitle,
    errorMessage,
    noMargin,
    label,
    dropzoneClassName,
    activeClassName,
    acceptClassName,
    rejectClassName,
    disabled,
    disabledClassName,
    maxSize,
    accept,
    small,
    meta: { error, touched },
    noFileName,
    inputs,
    upload,
    uploadPrefix,
    noUnique,
    treatmentFile,
    saveValue,
    onChange,
    height,
    iconUpload,
    iconDropzoneClassName,
    // text
    noInfo,
    noIcon,
    info,
    infoClassName,
    subinfo,
    subInfoClassName,
    children,
    withLibrary,
    //
    renderPreviewTop,
    displayName,
    displayValue,
    fileItem: FileItem,
    iconRemove,
    oneFile,
    previewType,
    // file preview
    filePreviewClassName,
    picturePreviewClassName,
    previewClassName,
    iconClassName,
    progressBarClassName,
    errorIconClassName,
    fileIconClassName,
    iconFile,
    iconError,
    button: ButtonComponent = Button,
    s3Upload,
    uploadParams,
    multipleUpload = false,
    keepId = false,
    // can be used with one file
    noDelete,
  } = props;

  let value = input.value;
  if (value && !Array.isArray(value)) value = [value];

  // file to display
  const [files, setFiles] = useState(value);

  const syncChange = list => {
    if (!list) return;

    let inputValue = [];

    if (oneFile) {
      inputValue = list[0];

      if (!inputValue || inputValue.progressInfo || inputValue.error) return;

      if (!keepId) inputValue._id = undefined;

      input.onChange(inputValue);

      if (onChange) onChange(inputValue);
      return;
    }

    for (let i = 0; i < list.length; i += 1) {
      const item = list[i];

      if (item && !item.progressInfo && !item.error) {
        if (keepId) inputValue.push(item);
        else inputValue.push(deepOmit(item, '_id'));
      }
    }

    if (isEqual(input.value, inputValue)) {
      return;
    }

    input.onChange(inputValue);

    if (onChange) onChange(inputValue);
  };

  useEffect(() => {
    const comparison = oneFile ? files[0] : files;

    if (!isEqual(input.value, comparison)) {
      setFiles(input.value);
    }
  }, [input?.value]);

  useEffect(() => {
    syncChange(files);
  }, [files]);

  // mapStateToProps
  const lg = useSelector((state: StoreState) => state.content.lg);
  // Allow to dispatch actions
  const dispatch = useDispatch();

  const dialogShow = data => dispatch(app.actions.dialog.show.action(data));
  const dialogHide = data => dispatch(app.actions.dialog.hide.action(data));
  const uploadS3 = data => dispatch(fileActions.uploadS3.request.action(data));

  const sizeIsManaged = file => {
    const stringLength = file.length - 'data:image/png;base64,'.length;

    const sizeInBytes = 4 * Math.ceil(stringLength / 3) * 0.5624896334383812;

    const sizeInMB = parseFloat((sizeInBytes / 1024 ** 2).toFixed(2));

    if (sizeInMB >= 6) return false;
    return true;
  };

  const setFile = (currentFiles, file, index = 0) => {
    if (!file) return;

    // Important to clone after for multiple files at the same time
    currentFiles[index] = file;

    const newFiles = cloneDeep(currentFiles);

    setFiles(newFiles);
  };

  // handle request
  // currentFiles is the list of currently handled files (avoid override of the list)
  const handleRequest = (currentFiles, file, index, binary) => {
    const fileData = {
      name: file.name,
      binary,
      progressInfo: {
        progress: 0,
      },
    };

    setFile(currentFiles, fileData, index);

    return fileData;
  };

  // multiple request at once
  const handleMultipleRequest = (currentFiles, newFiles) => {
    for (let index = 0; index < newFiles.length; index++) {
      const item = newFiles[index];
      const fileData = {
        name: item.name,
        progressInfo: {
          progress: 0,
        },
      };
      currentFiles.push(fileData);
    }
    const listFiles = cloneDeep(currentFiles);

    setFiles(listFiles);
  };

  // handle progress
  // file data must be the object returned by handle request
  const handleProgress = (currentFiles, progressData, index) => {
    if (!progressData) return;

    const progressInfo = {};

    progressInfo.progress = Math.round((100 * progressData.loaded) / progressData.total);

    const fileData = currentFiles[index];

    fileData.progressInfo = progressInfo;

    setFile(currentFiles, fileData, index);
  };

  // Handle file error
  const handleError = (currentFiles, index, err) => {
    const fileData = currentFiles[index];

    fileData.error = true;
    fileData.progressInfo = null;

    console.warn('Error file', err);

    setFile(currentFiles, fileData, index);
  };
  // =======

  // handle response (from server or file system)
  // res has no progressData
  const handleResponse = (currentFiles, res, index) => {
    let newValue = res;
    if (typeof treatmentFile === 'function') {
      newValue = treatmentFile(res);
    }

    if (saveValue && res[saveValue]) newValue = res[saveValue];

    setFile(currentFiles, newValue, index);
  };
  // multiple res at once
  const handleMultipleResponse = (currentFiles, res, baseIndex) => {
    for (let index = 0; index < res.length; index++) {
      const newFile = res[index];
      let newValue = newFile;
      if (typeof treatmentFile === 'function') {
        newValue = treatmentFile(newFile);
      }
      if (saveValue && newFile[saveValue]) newValue = newFile[saveValue];
      currentFiles[baseIndex + index] = newValue;
    }

    const newFiles = cloneDeep(currentFiles);

    setFiles(newFiles);
  };
  // =======

  // Handle upload for one file
  const handleS3Upload = (currentFiles, file, fileIndex) => {
    handleRequest(currentFiles, file, fileIndex);

    // get upload url
    uploadS3({
      data: {
        ...uploadParams,
        fileName: !noFileName && getFileName(uploadPrefix, file, noUnique),
        file,
      },
      noLoading: true,
      progress: progressData => handleProgress(currentFiles, progressData, fileIndex),
      callback: res => {
        console.log('RESPONSE', res);
        handleResponse(currentFiles, res, fileIndex);
      },
      callbackError: err => {
        handleError(currentFiles, fileIndex, err);
      },
    });
  };

  // Handle auto upload for one file
  const handleAutoUpload = (currentFiles, file, binary, fileIndex) => {
    if (s3Upload) {
      handleS3Upload(file, fileIndex);
      return false;
    }

    // Base64 upload
    if (!sizeIsManaged(binary)) {
      // eslint-disable-next-line
      window.alert('Size of this file is too large , Please select an image smaller than 6MB');
      return true;
    }

    if (!upload) return false;

    handleRequest(currentFiles, file, fileIndex, binary);

    upload({
      data: {
        ...inputs,
        title: !noFileName ? getFileName(uploadPrefix, file, noUnique) : undefined,
        base64: binary,
      },
      noLoading: true,
      progress: progressData => handleProgress(currentFiles, progressData, fileIndex),
      callback: res => {
        // handle Response update input and therefore reset progress
        handleResponse(currentFiles, res, fileIndex);
      },
      callbackError: err => {
        handleError(currentFiles, fileIndex, err);
      },
    });

    return true;
  };

  // Handle multiple S3 upload at once
  const handleMultipleS3Upload = acceptedFiles => {
    const currentFiles = files || [];
    const beforeFiles = cloneDeep(currentFiles);

    const currentIndex = currentFiles.length;

    handleMultipleRequest(currentFiles, acceptedFiles);

    const toUpload = [];
    acceptedFiles.forEach(item => {
      toUpload.push({
        ...uploadParams,
        fileName: !noFileName && getFileName(uploadPrefix, item, noUnique),
        file: item,
      });
    });

    // get upload url
    uploadS3({
      data: toUpload,
      noLoading: true,
      progress: (progressData, fileIndex) => {
        handleProgress(currentFiles, progressData, currentIndex + fileIndex);
      },
      callback: res => {
        console.log('MULTIPLE RESPONSE', res);
        handleMultipleResponse(currentFiles, res, currentIndex);
      },
      callbackError: (err, fileIndex) => {
        if (fileIndex === undefined) setFiles(beforeFiles);
        else handleError(currentFiles, currentIndex + fileIndex, err);
      },
    });
  };

  const onDrop = (acceptedFiles: File[], rejectedFiles: File[]) => {
    // do stuff with files... console.log(acceptedFiles)

    if (rejectedFiles.length > 0) {
      console.log('rej', rejectedFiles);
      dialogShow({
        id: 'error',
        title: errorTitle || 'Wrong format',
        messages: [errorMessage || 'The file type you used is not accepted'],
      });
      return;
    }

    if (acceptedFiles.length === 0) {
      return;
    }

    const keys = Object.keys(acceptedFiles);

    let nextIndex = files.length || 0;

    if (oneFile) nextIndex = 0;

    if (s3Upload && multipleUpload) {
      handleMultipleS3Upload(acceptedFiles);
      return;
    }

    // state before send
    const currentFiles = cloneDeep(files) || [];

    for (let i = 0; i < keys.length; i += 1) {
      const key = keys[i];
      const file = acceptedFiles[key];

      if (oneFile && key > 0) return;

      const current = nextIndex;
      nextIndex++;

      if (s3Upload) {
        handleS3Upload(currentFiles, file, current);
        continue;
      }

      const reader = new FileReader();

      reader.onload = () => {
        const fileAsBinaryString = reader.result;
        if (!handleAutoUpload(currentFiles, file, fileAsBinaryString, current)) {
          // attach base64 to the file
          file.base64 = fileAsBinaryString;
          handleResponse(currentFiles, file, current);
        }
      };

      reader.onabort = () => console.log('file reading was aborted');
      reader.onerror = () => console.log('file reading has failed');
      reader.readAsDataURL(file);
    }
  };

  const selectFromLocal = () => {
    const currentFiles = cloneDeep(files) || [];
    dialogShow({
      id: 'custom',
      medium: true,
      children: (
        <LibraryImages
          onSelect={res => {
            handleResponse(currentFiles, res, files.length - 1);
            dialogHide('custom');
          }}
        />
      ),
    });
  };

  const removeFile = index => {
    let values = files;

    values = cloneDeep(values);
    values.splice(index, 1);

    setFiles(values);
  };

  const renderFile = (file, index) => {
    if (!file) return false;

    const fileName =
      file && (file[displayName] || file.name || file.title || file.filename || file.fileName);

    const fileUrl = file && (file[displayValue] || file.getUrl || file.url);

    const binary = file?.binary;

    const progressInfo = file.progressInfo;

    if (FileItem)
      return (
        <FileItem key={index} name={fileName} url={fileUrl} progress={progressInfo?.progress} />
      );

    if (!previewType || previewType === 'file') {
      return (
        <div className={styles('file-preview', filePreviewClassName)} key={index}>
          <div className={styles('left')}>
            {file.error && (
              <div className={styles('icon', iconClassName, errorIconClassName, 'error')}>
                {iconError || <BiErrorCircle />}
              </div>
            )}
            {!file.error && fileUrl && (
              <a
                href={fileUrl}
                target="_blank"
                rel="noopener noreferrer"
                className={styles('icon', iconClassName, fileIconClassName, 'file', 'action')}
              >
                {iconFile || <FaFileAlt />}
              </a>
            )}
            {!file.error && !fileUrl && (
              <div className={styles('icon', iconClassName, fileIconClassName, 'file')}>
                {iconFile || <FaFileAlt />}
              </div>
            )}
            <TextItem path={fileName || 'Document'} />
          </div>
          {!noDelete && (
            <div
              className={styles('icon', iconClassName, 'action')}
              onClick={() => removeFile(index)}
            >
              {iconRemove || <MdClear />}
            </div>
          )}
          {progressInfo && (
            <div className={styles('progress-bar', progressBarClassName)}>
              <div
                className={styles('progress')}
                style={{
                  width: `${progressInfo.progress}%`,
                }}
              />
            </div>
          )}
        </div>
      );
    }

    if (previewType === 'picture') {
      return (
        <div className={styles('picture-preview', picturePreviewClassName)} key={index}>
          {!noDelete && (
            <div
              className={styles('icon', iconClassName, 'action')}
              onClick={() => removeFile(index)}
            >
              {iconRemove || <MdClear />}
            </div>
          )}
          <img src={fileUrl || binary} alt={fileName} />
          {progressInfo && (
            <div className={styles('progress-bar', progressBarClassName)}>
              <div
                className={styles('progress')}
                style={{
                  width: `${progressInfo.progress}%`,
                }}
              />
            </div>
          )}
        </div>
      );
    }

    // TODO handle image preview and video preview

    return false;
  };

  const renderFiles = () => {
    if (!files?.map) return false;

    return (
      <div className={styles('files', previewType, previewClassName)}>{files.map(renderFile)}</div>
    );
  };

  return (
    <FormElement {...props} className={styles('multipe-upload-zone', className)} info="">
      {label ? <FormItemLabel {...props} label={label} /> : false}
      {renderPreviewTop && renderFiles()}
      <Dropzone
        multiple={!oneFile}
        maxSize={maxSize}
        accept={accept}
        onDrop={onDrop}
        disabled={disabled}
      >
        {({ getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject }) => (
          <div
            className={styles('content')}
            {...getRootProps({
              className: styles(
                'Dropzone',
                dropzoneClassName,
                error && touched ? 'error' : false,
                small && 'small',
                !label && 'no-label',
                isDragActive && 'active',
                isDragActive && activeClassName,
                isDragAccept && 'accept',
                isDragAccept && acceptClassName,
                isDragReject && 'reject',
                isDragReject && rejectClassName,
                disabled && 'disabled',
                disabled && disabledClassName,
              ),
              style: {
                height,
              },
            })}
          >
            <input {...getInputProps()} />
            <div className={styles('dropzone-content')}>
              {!noIcon &&
                (iconUpload || (
                  <span className={styles('icon', iconDropzoneClassName)}>
                    <BiUpload />
                  </span>
                ))}
              {!noInfo && (
                <div className={styles('info', small && 'small', infoClassName)}>
                  <TextItem path={info || 'general.messages.upload'} />
                </div>
              )}
              {subinfo && (
                <div className={styles('subinfo', small && 'small', subInfoClassName)}>
                  <TextItem path={subinfo} />
                </div>
              )}
              {children}
            </div>
          </div>
        )}
      </Dropzone>
      {!renderPreviewTop && renderFiles()}
      {withLibrary && (
        <div>
          <ButtonComponent action={() => selectFromLocal()} label="Select from site library" />
        </div>
      )}
    </FormElement>
  );
};

export default MultipleUploadZone;
