/**
 * @file 文件上传弹窗组件 <默认支持xls,xlsx上传>
 * @author luozhengxuan <51071534@qq.com>
 * */
import React, { useCallback, useMemo, useState, useImperativeHandle } from 'react';
import classNames from 'classnames';
import Axios from 'axios';
import io from 'socket.io-client';
import { DeleteOutlined, UploadOutlined } from '@ant-design/icons';
import locale from 'antd/es/locale/zh_CN';
import ConfigProvider from 'antd/es/config-provider';
import Modal from 'antd/es/modal';
import 'antd/es/modal/style/index';
import Button from 'antd/es/button';
import 'antd/es/button/style/index';
import Upload from 'antd/es/upload';
import 'antd/es/upload/style/index';
import Progress from 'antd/es/progress';
import 'antd/es/progress/style/index';
import message from 'antd/es/message';
import 'antd/es/message/style/index';
import Form from 'antd/es/form';
import 'antd/es/form/style/index';
import { checkType, loadFile } from '@jy-fe/utils';
import { env } from '@jy-fe/business';

import { XuiModalFileProps, XuiModalFileHandles } from './xui-modal-file.d';

export const XuiModalFile: React.ForwardRefRenderFunction<
  XuiModalFileHandles,
  XuiModalFileProps
> = (
  {
    modalStyle = 'admin',
    title: ModalTilte,
    hideDownloadTemplate = false,
    stepNodes = [],
    fileStepTitles = ['选择文件', '导入数据'],
    loadTip = '请先下载Excel模板',
    tip = '选择 Microsoft Office Excel 文件（支持xls, xlsx格式）',
    downloadHref,
    uploadURL,
    uploadExtraConf = {},
    typeIntercept = {
      accept: [
        'application/vnd.ms-excel',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      ],
      acceptName: ['xls', 'xlsx'],
    },
    maxSize,
    maxSizeTip,
    isTureProgress = false,
    isCover,
    success,
    fail,
    afterClose,
  },
  ref,
) => {
  const classname = 'xui-ant__modal-file';

  const [visible, setVisible] = useState(false);
  const [step, setStep] = useState(0);
  const [nextStepLoading, setNextStepLoading] = useState(false);
  const [uploadLoading, setUploadLoading] = useState(false);
  const [stepDataMap, setStepDataMap] = useState(new Map());
  const [fileList, setFileList] = useState<any[]>([]);
  const [percent, setPercent] = useState('0');
  const [isimport, setIsimport] = useState(false);
  const [progressStatus, setProgressStatus] = useState<
    'normal' | 'exception' | 'active' | 'success'
  >('active');
  const [progressTip, setProgressTip] = useState('');
  const [resData, setResData] = useState<{ [key: string]: any } | null>(null);

  const [form] = Form.useForm();

  const stepTitle = useMemo(() => stepNodes.map(item => item.title).concat(fileStepTitles), [
    stepNodes,
    fileStepTitles,
  ]);

  /** 额外步骤的相关渲染dom集合 */
  const stepMap = useMemo(() => {
    const currentStepNodeMap: Map<number, JSX.Element> = new Map();
    const currentStepHandleMap: Map<
      number,
      ((values: { [key: string]: any }) => Promise<any>) | undefined
    > = new Map();
    const formNodes: JSX.Element[] = [];
    stepNodes.forEach((item, index) => {
      const { title, defaultValues, formInfo, onNextStep } = item;
      currentStepHandleMap.set(index, onNextStep);
      if (formInfo.length > 0) {
        const formProps: any = {
          className: classNames(`${classname}_${modalStyle}--form`),
          key: title,
          form,
          labelCol: { span: 6 },
          wrapperCol: { span: 16 },
        };
        if (defaultValues) {
          formProps.initialValues = defaultValues;
        }
        formNodes.push(
          <Form {...formProps}>
            {formInfo.map(f => {
              const { label, name, rules, children, extra } = f;
              return (
                <Form.Item key={name} label={label} name={name} rules={rules} extra={extra}>
                  {children}
                </Form.Item>
              );
            })}
          </Form>,
        );
      }
    });
    formNodes.forEach((item, index) => {
      currentStepNodeMap.set(index, item);
    });
    return {
      stepNodeMap: currentStepNodeMap,
      stepHandleMap: currentStepHandleMap,
    };
  }, [stepNodes, form, modalStyle]);

  const internalBeforeUpload = (file: any) => {
    const { accept, acceptName } = typeIntercept;

    // 部分文件获取的type有可能为''
    // 用后缀名判断
    if (file.type) {
      const n = file.name;
      const extension = n.split('.').pop();
      if (!acceptName.includes(extension)) {
        message.error(`只支持${acceptName.join('、')}格式`);
        return false;
      }
    }

    if (file.type && !accept.includes(file.type)) {
      message.error(`只支持${acceptName.join('、')}格式`);
      return false;
    }

    if (maxSize && maxSizeTip && file.size > maxSize) {
      message.error(maxSizeTip);
      return false;
    }

    if (fileList.length >= 1) {
      if (isCover) {
        setFileList([file]);
      } else {
        message.warn('一次仅支持上传一个文件');
      }
    } else {
      setFileList([...fileList, file]);
    }

    return false;
  };

  // eslint-disable-next-line consistent-return
  const fetchCreateUpload = async () => {
    try {
      const { data: extraData, ...otherExtraConf } = uploadExtraConf;
      const formData = new FormData();
      if (extraData instanceof Object) {
        Object.entries(extraData).forEach(entry => {
          formData.append(entry[0], entry[1]);
        });
      }
      fileList.forEach(file => {
        formData.append('file', file);
      });
      setUploadLoading(true);

      if (isTureProgress) {
        // 真实进度条
        const socket = io.connect(env.jySaasSocketURL, {
          transports: ['websocket', 'polling'],
          reconnection: true,
        });

        socket.on('sessionId:', async (sessionId: string) => {
          formData.append('sessionId', sessionId);
          const { data } = await Axios({
            url: uploadURL,
            method: 'POST',
            data: formData,
            ...(otherExtraConf || {}),
          });
          // 这里如果使用 return Promise.reject 会进入到 axios 的捕获里
          // TODO: 待优化
          if (data.status) {
            throw data;
          } else {
            setUploadLoading(false);
            setStep(stepTitle.length - 1);
          }
          return data;
        });
        socket.on(
          'message:',
          async (
            progressRes: {
              status: string;
              percentage: string;
              msg: string;
            },
            resultRes: {
              allNum: number;
              errorNum: number;
              errorMsg: string;
              rightNum: number;
              url: string;
            },
          ) => {
            const { status, percentage, msg } = progressRes;
            const progressStatusMap = new Map([
              ['wait', 'active'],
              ['success', 'success'],
              ['fail', 'exception'],
            ]);
            setUploadLoading(false);
            setStep(stepTitle.length - 1);
            setPercent(percentage);
            setProgressTip(percentage === '100' ? '' : msg);
            setIsimport(status === 'wait');
            setProgressStatus(progressStatusMap.get(status) as any);
            setResData(resultRes);
            if (percentage === '100') {
              socket.disconnect();
              if (success) success(resultRes);
            }
          },
        );
      } else {
        const { data } = await Axios({
          url: uploadURL,
          method: 'POST',
          data: formData,
          ...(otherExtraConf || {}),
        });
        // 这里如果使用 return Promise.reject 会进入到 axios 的捕获里
        // TODO: 待优化
        if (data.status) {
          throw data;
        } else {
          setUploadLoading(false);
          setStep(stepTitle.length - 1);
          setPercent('100');
          setProgressStatus('success');
          setResData(data.data);
          message.success('上传成功');
          if (success) success(data.data);
        }
        return data;
      }
    } catch (err) {
      setUploadLoading(false);
      setPercent('0');
      if (err.msg) {
        message.warn(err.msg);
      } else {
        message.error('网络异常');
      }
      if (fail) fail(err);
    }
  };

  const handleDeleteFile = (index: number) => {
    const currentFileList = fileList.slice();
    currentFileList.splice(index, 1);
    setFileList(currentFileList);
  };

  const hanldeNextStep = useCallback(() => {
    form.validateFields().then(async values => {
      let through = true;
      const f = stepMap.stepHandleMap.get(step);
      if (f && checkType(f) === 'Function') {
        setNextStepLoading(true);
        try {
          await f(values);
          setNextStepLoading(false);
        } catch (error) {
          through = false;
          setNextStepLoading(false);
        }
      }
      if (through) {
        stepDataMap.set(step, values);
        setStepDataMap(stepDataMap);
        setStep(step + 1);
      }
    });
  }, [step, form, stepMap, stepDataMap]);

  const hanldeBackStep = useCallback(() => {
    setStep(step - 1);
  }, [step]);

  const handleClose = useCallback(() => {
    setVisible(false);
  }, []);

  const reset = useCallback(() => {
    setVisible(false);
    setStep(0);
    setNextStepLoading(false);
    setUploadLoading(false);
    setStepDataMap(new Map());
    setFileList([]);
    setPercent('0');
    setProgressStatus('active');
    setProgressTip('');
    setResData(null);
  }, []);

  const onAfterClose = useCallback(() => {
    form.resetFields();
    reset();
    afterClose && afterClose();
  }, [form, afterClose, reset]);

  useImperativeHandle(ref, () => ({
    form,
    show: () => setVisible(true),
    destory: () => setVisible(false),
    changeNextStepLoading: value => setNextStepLoading(value),
  }));

  return (
    <Modal
      title={ModalTilte}
      width={modalStyle === 'admin' ? 640 : 500}
      visible={visible}
      onCancel={handleClose}
      maskClosable={false}
      destroyOnClose
      afterClose={onAfterClose}
      footer={
        <div>
          {/* 不是最后一步时才显示取消按钮 */}
          {step !== stepTitle.length - 1 && <Button onClick={handleClose}>取消</Button>}
          {/* 除第一步和最后一步都有上一步按钮 */}
          {step !== 0 && step !== stepTitle.length - 1 && (
            <Button onClick={hanldeBackStep}>上一步</Button>
          )}
          {/* 只有总步数超过2步，且当前不为最后两步时显示下一步 */}
          {stepTitle.length > 2 && step < stepTitle.length - 2 && (
            <Button type='primary' onClick={hanldeNextStep} loading={nextStepLoading}>
              下一步
            </Button>
          )}
          {/* 只有倒数第二步时才显示导入按钮 */}
          {step === stepTitle.length - 2 && (
            <Button
              type='primary'
              onClick={fetchCreateUpload}
              disabled={!fileList.length}
              loading={uploadLoading}
            >
              导入
            </Button>
          )}
          {/* 只有最后一步时才显示导入按钮 */}
          {step === stepTitle.length - 1 && (
            <Button type='primary' onClick={handleClose} disabled={isimport}>
              完成
            </Button>
          )}
        </div>
      }
    >
      <ConfigProvider locale={locale}>
        {modalStyle === 'admin' ? (
          <>
            <div className={classNames(`${classname}_${modalStyle}--step-wrap`)}>
              {stepTitle.map((item, index) => (
                <div
                  key={String(index)}
                  className={classNames({
                    [`${classname}_${modalStyle}--step-item`]: true,
                    [` ${classname}_${modalStyle}--step-item-active`]: index === step,
                    [` ${classname}_${modalStyle}--step-item-nofirst-active`]:
                      index === step && index !== 0,
                    [` ${classname}_${modalStyle}--step-item-nolast-active`]:
                      index === step && index !== stepTitle.length - 1,
                  })}
                >
                  {item}
                </div>
              ))}
            </div>

            {stepTitle.length > 2 && step < stepTitle.length - 2 && stepMap.stepNodeMap.get(step)}

            {step === stepTitle.length - 2 ? (
              <div className={`${classname}_${modalStyle}--content-wrap`}>
                <p>
                  选择 Excel{' '}
                  {!hideDownloadTemplate && (
                    <a onClick={() => loadFile(downloadHref)}>下载导入模板</a>
                  )}
                </p>
                <div className={`${classname}_${modalStyle}--btn-wrap`}>
                  <Upload
                    beforeUpload={internalBeforeUpload}
                    fileList={fileList}
                    // 2020.6.3发现一台装wps的mac上传时无法选中excel文件，在未装wps的mac上正常
                    // 经调试，单纯接受application/vnd.openxmlformats-officedocument.spreadsheetml.sheet正常
                    // application/vnd.ms-excel不能正常接受，所以这里暂时改为通过后缀名接受
                    accept={typeIntercept.acceptName.map(type => `.${type}`).join(',')}
                  >
                    <Button>点击选择文件</Button>
                  </Upload>
                </div>
                <p className={`${classname}_${modalStyle}--tip`}>{tip}</p>

                <div className={`${classname}--file-list`}>
                  {fileList.map((file, i) => (
                    <p className={`${classname}--file`} key={file.uid}>
                      {file.name}
                      <a onClick={() => handleDeleteFile(i)}>
                        <DeleteOutlined />
                      </a>
                    </p>
                  ))}
                </div>
              </div>
            ) : null}
          </>
        ) : (
          <>
            {step === stepTitle.length - 2 ? (
              <div className={`${classname}_${modalStyle}--content-wrap`}>
                <div className={`${classname}_${modalStyle}--step-wrap`}>
                  <div className={`${classname}_${modalStyle}--step-label`}>步骤一：</div>
                  <div className={`${classname}_${modalStyle}--step-content`}>
                    <div>
                      <a onClick={() => loadFile(downloadHref)}>下载导入模板</a>
                    </div>
                    <div className={`${classname}_${modalStyle}--step-content-tip`}>{loadTip}</div>
                  </div>
                </div>
                <div className={`${classname}_${modalStyle}--step-wrap`}>
                  <div className={`${classname}_${modalStyle}--step-label`}>步骤二：</div>
                  <div className={`${classname}_${modalStyle}--step-content`}>
                    <div className={`${classname}_${modalStyle}--step-content-btn`}>
                      <Upload
                        beforeUpload={internalBeforeUpload}
                        fileList={fileList}
                        accept={typeIntercept.acceptName.map(type => `.${type}`).join(',')}
                      >
                        <Button>
                          <UploadOutlined />
                          上传文件
                        </Button>
                      </Upload>
                    </div>
                    <p className={`${classname}_${modalStyle}--step-content-tip`}>{tip}</p>
                    <div className={`${classname}--file-list`}>
                      {fileList.map((file, i) => (
                        <p className={`${classname}--file`} key={file.uid}>
                          {file.name}
                          <a onClick={() => handleDeleteFile(i)}>
                            <DeleteOutlined />
                          </a>
                        </p>
                      ))}
                    </div>
                  </div>
                </div>
              </div>
            ) : null}
          </>
        )}

        {step === stepTitle.length - 1 ? (
          <div className={`${classname}--result-wrap`}>
            <Progress percent={Number(percent)} status={progressStatus} />
            {progressTip}
            {!progressTip && resData ? (
              <p>
                {resData.errorMsg ||
                  `总计导入${resData.allNum}条数据，成功${resData.rightNum}条，失败${resData.errorNum}
                条。`}
                &nbsp;&nbsp;&nbsp;
                {resData.errorNum ? (
                  <a onClick={() => loadFile(resData.url)}>下载错误报告</a>
                ) : null}
              </p>
            ) : null}
          </div>
        ) : null}
      </ConfigProvider>
    </Modal>
  );
};

export default React.forwardRef(XuiModalFile);
