import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible';
import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString, parseJsonText } from '@/utils';
import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { to } from '@/utils/promise';
import G6, { Util } from '@antv/g6';
import { Button } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import ExperimentDrawer from '../components/ExperimentDrawer';
import ParamsModal from '../components/ViewParamsModal';
import { experimentStatusInfo } from '../status';
import styles from './index.less';

let graph = null;
const NodePrefix = 'workflow';

function ExperimentText() {
  const [experimentIns, setExperimentIns] = useState(undefined);
  const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined);
  const [workflowStatus, setWorkflowStatus] = useState(undefined);
  const graphRef = useRef();
  const workflowRef = useRef();
  const locationParams = useParams(); // 新版本获取路由参数接口
  const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);
  const [propsDrawerOpen, openPropsDrawer, closePropsDrawer, propsDrawerOpenRef] =
    useVisible(false);
  const navigate = useNavigate();
  const evtSourceRef = useRef();
  const width = 110;
  const height = 36;
  const status = getExperimentInstanceStatus(experimentIns?.status, workflowStatus);
  const statusInfo = experimentStatusInfo[status];

  useEffect(() => {
    initGraph();
    getWorkflow();

    return () => {
      if (evtSourceRef.current) {
        evtSourceRef.current.close();
        evtSourceRef.current = null;
      }
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    const changeSize = () => {
      if (!graph || graph.get('destroyed')) return;
      if (!graphRef.current) return;
      graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight);
      graph.fitView();
    };

    window.addEventListener('resize', changeSize);
    return () => {
      window.removeEventListener('resize', changeSize);
    };
  }, []);

  // 获取流水线模版
  const getWorkflow = async () => {
    const [res] = await to(getWorkflowById(locationParams.workflowId));
    if (res && res.data && res.data.dag) {
      try {
        const dag = JSON.parse(res.data.dag);
        dag.nodes.forEach((item) => {
          item.in_parameters = JSON.parse(item.in_parameters);
          item.out_parameters = JSON.parse(item.out_parameters);
          item.control_strategy = JSON.parse(item.control_strategy);
          item.imgName = item.img.slice(0, item.img.length - 4);
        });
        workflowRef.current = dag;
        getExperimentInstance();
      } catch (error) {
        // JSON.parse 错误
        console.error('JSON.parse error: ', error);
      }
    }
  };

  // 获取实验实例
  const getExperimentInstance = async () => {
    const [res] = await to(getExperimentIns(locationParams.id));
    if (res && res.data && workflowRef.current) {
      setExperimentIns(res.data);
      const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time } = res.data;
      const workflowData = workflowRef.current;
      const experimentStatusObjs = parseJsonText(nodes_status);
      if (experimentStatusObjs) {
        // 更新各个节点
        workflowData.nodes.forEach((item) => {
          const experimentNode = experimentStatusObjs?.[item.id];
          updateWorkflowNode(item, experimentNode);
        });

        // 设置 workflow 总状态
        Object.keys(experimentStatusObjs).some((key) => {
          if (key.startsWith(NodePrefix)) {
            const tempWorkflowStatus = experimentStatusObjs[key];
            setWorkflowStatus(tempWorkflowStatus);
            return true;
          }
          return false;
        });
      }

      // 绘制图
      getGraphData(workflowData, true);

      if (status === ExperimentStatus.Pending) {
        // 如果状态是 Pending, 打开第一个节点
        const node = workflowData.nodes[0];
        if (node) {
          setExperimentNodeData(node);
          openPropsDrawer();
        }
      } else if (status === ExperimentStatus.Running) {
        // 如果状态是 Running，打开第一个运行中的节点，如果没有运行中的节点，则打开第一个节点
        const node =
          workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ??
          workflowData.nodes[0];
        if (node) {
          setExperimentNodeData(node);
          openPropsDrawer();
        }
      }

      // 运行中或者等待中，开启 SSE
      if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
        setupSSE(argo_ins_name, argo_ins_ns);
      }
    }
  };

  const setupSSE = (name, namespace) => {
    const { origin } = location;
    const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
    const evtSource = new EventSource(
      `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
      { withCredentials: false },
    );
    evtSource.onmessage = (event) => {
      const data = event?.data;
      if (!data) {
        return;
      }
      try {
        const dataJson = JSON.parse(data);
        const statusData = dataJson?.result?.object?.status;
        if (!statusData) {
          return;
        }
        const { finishedAt, phase, nodes = {} } = statusData;
        
        // 更新实验实例状态和结束时间
        // setExperimentIns((prev) => ({
        //   ...prev,
        //   finish_time: finishedAt,
        //   status: phase,
        // }));

        // 设置总 workflow 状态
        const tempWorkflowStatus = Object.values(nodes).find((node) =>
          node.displayName.startsWith(NodePrefix),
        );
        if (tempWorkflowStatus) {
          setWorkflowStatus(tempWorkflowStatus);
        }

        // 更新各个节点
        const workflowData = workflowRef.current;
        workflowData.nodes.forEach((item) => {
          const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
          updateWorkflowNode(item, experimentNode);
        });

        // 绘制图
        getGraphData(workflowData, false);

        // 更新打开的抽屉数据
        if (propsDrawerOpenRef.current && experimentNodeDataRef.current) {
          const currentId = experimentNodeDataRef.current.id;
          const node = workflowData.nodes.find((item) => item.id === currentId);
          if (node) {
            setExperimentNodeData(node);
          }
        }
        if (phase !== ExperimentStatus.Pending && phase !== ExperimentStatus.Running) {
          evtSource.close();
          evtSourceRef.current = null;
        }
      } catch (error) {
        console.error('JSON.parse error: ', error);
      }
    };
    evtSource.onerror = (error) => {
      console.error('SSE error: ', error);
    };

    evtSourceRef.current = evtSource;
  };

  // 更新各个节点
  function updateWorkflowNode(workflowNode, statusNode) {
    if (!statusNode) {
      return;
    }
    const { finishedAt, startedAt, phase, id, message } = statusNode;
    workflowNode.experimentStartTime = startedAt;
    workflowNode.experimentEndTime = finishedAt;
    workflowNode.experimentStatus = phase;
    workflowNode.workflowId = id;
    workflowNode.message = message;
    workflowNode.img = phase
      ? `${workflowNode.imgName}-${phase}.png`
      : `${workflowNode.imgName}.png`;
  }

  // 根据数据，渲染图
  const getGraphData = (data, first) => {
    if (graph) {
      const zoom = graph.getZoom();
      // 在拉取新数据重新渲染页面之前先获取点（0， 0）在画布上的位置
      const lastPoint = graph.getCanvasByPoint(0, 0);
      graph.data(data);
      graph.render();
      if (first) {
        graph.fitView();
      } else {
        graph.zoomTo(zoom);
        // 获取重新渲染之后点（0， 0）在画布的位置
        const newPoint = graph.getCanvasByPoint(0, 0);
        // 移动画布相对位移;
        graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y);
      }
    } else {
      setTimeout(() => {
        getGraphData(data, first);
      }, 500);
    }
  };

  const initGraph = () => {
    G6.registerNode(
      'rect-node',
      {
        // draw anchor-point circles according to the anchorPoints in afterDraw
        getAnchorPoints(cfg) {
          return (
            cfg.anchorPoints || [
              // 四个，上下左右
              [0.5, 0],
              [0.5, 1],
              [0, 0.5],
              [1, 0.5],
            ]
          );
        },
        afterDraw(cfg, group) {
          const image = group.addShape('image', {
            attrs: {
              x: -45,
              y: -10,
              width: 20,
              height: 20,
              img: cfg.img,
              cursor: 'pointer',
            },
            draggable: true,
          });
          if (cfg.label) {
            group.addShape('text', {
              attrs: {
                text: fittingString(cfg.label, 70, 10),
                x: -20,
                y: 0,
                fontSize: 10,
                textAlign: 'left',
                textBaseline: 'middle',
                fill: '#000',
                cursor: 'pointer',
              },
              name: 'text-shape',
              draggable: true,
            });
          }
          const hasRightImg =
            cfg.experimentStatus === ExperimentStatus.Pending ||
            cfg.experimentStatus === ExperimentStatus.Running;
          if (hasRightImg) {
            const image = group.addShape('image', {
              attrs: {
                x: -10,
                y: -10,
                width: 20,
                height: 20,
                img:
                  cfg.experimentStatus === ExperimentStatus.Pending
                    ? require('@/assets/img/experiment-pending.png')
                    : require('@/assets/img/experiment-running.png'),
                cursor: 'pointer',
              },
              draggable: false,
              capture: false,
            });

            if (cfg.experimentStatus === ExperimentStatus.Running) {
              image.animate(
                (ratio) => {
                  const toMatrix = Util.transform(
                    [1, 0, 0, 0, 1, 0, 0, 0, 1],
                    [
                      ['r', ratio * Math.PI * 2],
                      ['t', width / 2 - 14 + 10, -height / 2 - 6 + 10],
                    ],
                  );
                  return {
                    matrix: toMatrix,
                  };
                },
                {
                  repeat: true, // 动画重复
                  duration: 1000,
                  easing: 'easeLinear',
                },
              );
            } else if (cfg.experimentStatus === ExperimentStatus.Pending) {
              const toMatrix = Util.transform(
                [1, 0, 0, 0, 1, 0, 0, 0, 1],
                [['t', width / 2 - 14 + 10, -height / 2 - 6 + 10]],
              );
              image.setMatrix(toMatrix);
            }
          }
          const bbox = group.getBBox();
          const anchorPoints = this.getAnchorPoints(cfg);
          anchorPoints.forEach((anchorPos, i) => {
            group.addShape('circle', {
              attrs: {
                r: 3,
                x: bbox.x + bbox.width * anchorPos[0],
                y: bbox.y + bbox.height * anchorPos[1],
                fill: '#fff',
                stroke: '#a4a4a5',
              },
              name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point')
              anchorPointIdx: i, // flag the idx of the anchor-point circle
              links: 0, // cache the number of edges connected to this shape
              visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state
            });
          });
          return image;
        },

        // response the state changes and show/hide the link-point circles
        setState(name, value, item) {
          const group = item.getContainer();
          const shape = group.get('children')?.[0];
          if (name === 'hover') {
            if (value) {
              shape?.attr('stroke', themes['primaryColor']);
            } else {
              shape?.attr('stroke', 'transparent');
            }
          }
        },
      },
      'rect',
    );
    graph = new G6.Graph({
      container: graphRef.current,
      grid: true,
      width: graphRef.current.clientWidth || 500,
      height: graphRef.current.clientHeight || 760,
      animate: false,
      groupByTypes: true,
      enabledStack: false,
      fitView: true,
      minZoom: 0.5,
      maxZoom: 5,
      fitViewPadding: 200,
      modes: {
        default: [
          // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles
          {
            type: 'drag-node',
            shouldBegin: (e) => {
              if (e.target.get('name') === 'anchor-point') return false;
              return true;
            },
          },
          // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles
          'drag-canvas',
          'zoom-canvas',
        ],
      },

      defaultNode: {
        type: 'rect-node',
        size: [width, height],

        labelCfg: {
          style: {
            fill: '#000',
            fontSize: 10,
            cursor: 'pointer',
            x: -20,
            y: 0,
            textAlign: 'left',
            textBaseline: 'middle',
          },
        },
        style: {
          fill: '#fff',
          stroke: 'transparent',
          cursor: 'pointer',
          radius: 8,
          shadowColor: 'rgba(75, 84, 137, 0.4)',
          shadowBlur: 6,
          shadowOffsetX: 0,
          shadowOffsetY: 0,
          overflow: 'hidden',
          lineWidth: 0.5,
        },
      },
      defaultEdge: {
        style: {
          endArrow: {
            // 设置终点箭头
            path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数，参数为箭头的 宽度、长度、偏移量（默认为 0，与 d 对应）
            d: 4.5,
            fill: '#a2a6b5',
          },
          cursor: 'pointer',
          lineWidth: 1,
          opacity: 1,
          stroke: '#a2a6b5',
          radius: 1,
        },
        labelCfg: {
          autoRotate: true,
          style: {
            fontSize: 10,
            fill: '#FFF',
          },
        },
      },
    });

    // 修改历史数据样式问题
    graph.node((node) => {
      return {
        style: {
          stroke: 'transparent',
          radius: 8,
        },
      };
    });

    // 绑定事件
    bindEvents();
  };

  // 绑定事件
  const bindEvents = () => {
    const closeDrawer = () => {
      closePropsDrawer();
      setTimeout(() => {
        setExperimentNodeData(null);
      }, 200);
    };

    graph.on('node:click', (e) => {
      if (e.target.get('name') !== 'anchor-point' && e.item) {
        const model = e.item.getModel();
        setExperimentNodeData(model);
        openPropsDrawer();
      }
    });
    graph.on('node:mouseenter', (e) => {
      graph.setItemState(e.item, 'hover', true);
    });
    graph.on('node:mouseleave', (e) => {
      graph.setItemState(e.item, 'hover', false);
    });
    graph.on('canvas:click', (e) => {
      closeDrawer();
    });
    graph.on('edge:click', (e) => {
      closeDrawer();
    });
  };

  return (
    <div className={styles['pipeline-container']}>
      <div className={styles['pipeline-container__top']}>
        <div className={styles['pipeline-container__top__info']}>
          启动时间：{formatDate(workflowStatus?.startedAt)}
        </div>
        <div className={styles['pipeline-container__top__info']}>
          执行时长：
          <RunDuration
            createTime={workflowStatus?.startedAt}
            finishTime={workflowStatus?.finishedAt}
          />
        </div>
        <div className={styles['pipeline-container__top__info']}>
          状态：
          {statusInfo ? (
            <>
              <img
                style={{ width: '17px', marginRight: '7px' }}
                src={statusInfo.icon}
                draggable={false}
                alt=""
              />
              <span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
            </>
          ) : (
            '--'
          )}
        </div>
        <Button
          className={styles['pipeline-container__top__param-button']}
          onClick={openParamsModal}
        >
          执行参数
        </Button>
      </div>
      <div className={styles['pipeline-container__graph']} ref={graphRef}></div>
      {experimentIns && experimentNodeData ? (
        <ExperimentDrawer
          key={experimentNodeData.id}
          open={propsDrawerOpen}
          onClose={closePropsDrawer}
          pipelineId={Number(locationParams.workflowId)}
          experimentId={experimentIns.experiment_id}
          experimentName={experimentIns.experiment_name}
          instanceId={experimentIns.id}
          instanceName={experimentIns.argo_ins_name}
          instanceNamespace={experimentIns.argo_ins_ns}
          instanceNodeData={experimentNodeData}
          workflowId={experimentNodeData.workflowId}
          instanceNodeStatus={experimentNodeData.experimentStatus}
          instanceNodeStartTime={experimentNodeData.experimentStartTime}
          instanceNodeEndTime={experimentNodeData.experimentEndTime}
        ></ExperimentDrawer>
      ) : null}
      <ParamsModal
        open={paramsModalOpen}
        onCancel={closeParamsModal}
        globalParam={experimentIns?.global_param}
      ></ParamsModal>
    </div>
  );
}
export default ExperimentText;
