<script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref, toRaw } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table';
import DissectionTree from '../tshark/components/DissectionTree.vue';
import DissectionDump from '../tshark/components/DissectionDump.vue';
import TaskInfoIcon from './components/TaskInfoIcon.vue';
import {
  RESULT_CODE_ERROR,
  RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import { filePullTask, getTraceData, listTraceData } from '@/api/trace/task';
import { scriptUrl as wkUrl } from '@/assets/js/wiregasm_worker';
import * as wkUtil from '@/plugins/wk-worker';
import * as wsUtil from '@/plugins/ws-websocket';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import useTabsStore from '@/store/modules/tabs';
import saveAs from 'file-saver';
import { parseDateToStr } from '@/utils/date-utils';
const { getDict } = useDictStore();
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const tabsStore = useTabsStore();
const ws = new wsUtil.WS();
const wk = new wkUtil.WK();

/**跟踪编号 */
const traceId = ref<string>(route.query.traceId as string);
/**任务编号 */
const id = ref<string>(route.query.id as string);

/**关闭跳转 */
function fnClose() {
  const to = tabsStore.tabClose(route.path);
  if (to) {
    router.push(to);
  } else {
    router.back();
  }
}

/**字典数据 */
let dict: {
  /**跟踪消息类型 */
  traceMsgType: DictType[];
  /**跟踪消息方向 */
  traceMsgDirect: DictType[];
} = reactive({
  traceMsgType: [],
  traceMsgDirect: [],
});

/**下载触发等待 */
let downLoading = ref<boolean>(false);

/**信息文件下载 */
function fnDownloadFile() {
  if (downLoading.value) return;
  const fileName = `trace_${traceId.value}.pcap`;
  Modal.confirm({
    title: t('common.tipTitle'),
    content: t('views.logManage.neFile.downTip', { fileName }),
    onOk() {
      downLoading.value = true;
      const hide = message.loading(t('common.loading'), 0);
      filePullTask(traceId.value)
        .then(res => {
          if (res.code === RESULT_CODE_SUCCESS) {
            message.success({
              content: t('common.msgSuccess', {
                msg: t('common.downloadText'),
              }),
              duration: 2,
            });
            saveAs(res.data, `${fileName}`);
          } else {
            message.error({
              content: t('views.logManage.neFile.downTipErr'),
              duration: 2,
            });
          }
        })
        .finally(() => {
          hide();
          downLoading.value = false;
        });
    },
  });
}

// =========== 表格数据 ==============
/**表格状态类型 */
type TabeStateType = {
  /**加载等待 */
  loading: boolean;
  /**紧凑型 */
  size: SizeType;
  /**记录数据 */
  data: object[];
  /**点击选择行 */
  row: Record<string, any>;
};

/**表格状态 */
let tableState: TabeStateType = reactive({
  loading: false,
  size: 'small',
  data: [],
  row: {},
});

/**表格字段列 */
let tableColumns: ColumnsType = [
  {
    title: t('views.traceManage.task.msgNe'),
    dataIndex: 'msgNe',
    align: 'left',
    width: 150,
  },
  {
    title: t('views.traceManage.task.rowTime'),
    dataIndex: 'timestamp',
    align: 'left',
    width: 250,
    customRender(opt) {
      if (!opt.value) return '';
      const nanoseconds = opt.value; // 纳秒时间戳
      const milliseconds = Math.floor(nanoseconds / 1000000);
      const nanosecondsPart = (nanoseconds % 1000000)
        .toString()
        .padStart(6, '0');

      return `${parseDateToStr(
        milliseconds,
        'YYYY-MM-DD HH:mm:ss.SSS'
      )}.${nanosecondsPart}`;
    },
    sorter: true,
  },
  {
    title: t('views.traceManage.task.protocolOrInterface'),
    dataIndex: 'ifType',
    align: 'left',
    width: 150,
  },
  {
    title: t('views.traceManage.task.msgEvent'),
    dataIndex: 'msgEvent',
    align: 'left',
    ellipsis: true,
  },
  {
    title: t('views.traceManage.task.msgType'),
    dataIndex: 'msgType',
    key: 'msgType',
    align: 'left',
    width: 100,
  },
  {
    title: t('views.traceManage.task.msgDirect'),
    dataIndex: 'msgDirect',
    key: 'msgDirect',
    align: 'left',
    width: 100,
  },
  {
    title: t('views.traceManage.task.srcIp'),
    dataIndex: 'srcAddr',
    align: 'left',
    width: 150,
  },
  {
    title: t('views.traceManage.task.dstIp'),
    dataIndex: 'dstAddr',
    align: 'left',
    width: 150,
  },
];

/**表格分页器参数 */
let tablePagination = reactive({
  /**当前页数 */
  current: 1,
  /**每页条数 */
  pageSize: 10,
  /**默认的每页条数 */
  defaultPageSize: 10,
  /**指定每页可以显示多少条 */
  pageSizeOptions: ['10', '20', '50', '100'],
  /**只有一页时是否隐藏分页器 */
  hideOnSinglePage: false,
  /**是否可以快速跳转至某页 */
  showQuickJumper: true,
  /**是否可以改变 pageSize */
  showSizeChanger: true,
  /**数据总数 */
  total: 0,
  showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
  onChange: (page: number, pageSize: number) => {
    tablePagination.current = page;
    tablePagination.pageSize = pageSize;
    queryParams.pageNum = page;
    queryParams.pageSize = pageSize;
    fnGetList();
  },
});

/**表格分页、排序、筛选变化时触发操作, 排序方式，取值为 ascend descend */
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
  const { field, order } = sorter;
  if (order) {
    queryParams.sortBy = field;
    queryParams.sortOrder = order.replace('end', '');
  } else {
    queryParams.sortOrder = 'asc';
  }
  fnGetList(1);
}

/**查询参数 */
let queryParams = reactive({
  traceId: traceId.value,
  sortBy: 'timestamp',
  sortOrder: 'asc',
  /**当前页数 */
  pageNum: 1,
  /**每页条数 */
  pageSize: 10,
});
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
  if (tableState.loading) return;
  tableState.loading = true;
  if (pageNum) {
    queryParams.pageNum = pageNum;
  }

  listTraceData(toRaw(queryParams)).then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      const { total, rows } = res.data;
      tablePagination.total = total;
      tableState.data = rows;
      if (
        tablePagination.total <=
          (queryParams.pageNum - 1) * tablePagination.pageSize &&
        queryParams.pageNum !== 1
      ) {
        tableState.loading = false;
        fnGetList(queryParams.pageNum - 1);
      }
    }
    tableState.loading = false;
  });
}

/**
 * 查看帧数据
 * @param row 记录信息
 */
function fnVisible(row: Record<string, any>) {
  // 选中行重复点击时显示隐藏
  if (row.id === tableState.row.id && state.selectedFrame !== 0) {
    state.selectedFrame = 0;
    return;
  }
  getTraceData(row.id).then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      Object.assign(tableState.row, res.data);
      const blob = generatePCAP(res.data.timestamp / 1e3, res.data.rawMsg);
      wk.send({ type: 'process', file: blob });
    }
  });
}
/**生成PCAP-blob */
function generatePCAP(timestamp: number, base64Data: string): Blob {
  // 1. 转换数据
  const binaryString = atob(base64Data);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }

  // 2. 创建PCAP文件头
  const fileHeader = new Uint8Array([
    0xd4,
    0xc3,
    0xb2,
    0xa1, // magic_number (微秒级)
    0x02,
    0x00,
    0x04,
    0x00, // version_major(2) + version_minor(4)
    0x00,
    0x00,
    0x00,
    0x00, // thiszone (UTC)
    0x00,
    0x00,
    0x00,
    0x00, // sigfigs (固定0)
    0x00,
    0x00,
    0x04,
    0x00, // snaplen (1024)
    0x71,
    0x00,
    0x00,
    0x00, // network (LINKTYPE_LINUX_SLL)
  ]);

  // 3. 构造Linux cooked头
  const cookedHeader = new Uint8Array(16);
  const view = new DataView(cookedHeader.buffer);
  view.setUint16(0, 0x0000, false); // 数据包类型（主机→网络）
  view.setUint16(2, 0x0304, false); // 地址类型（ARPHRD_ETHER）
  view.setUint16(4, 0x0008, false); // 协议类型（ETH_P_IP）
  view.setUint16(14, 0x0800, false); // 数据包类型（PACKET_HOST）

  // 4. 合并链路层头与数据
  const fullData = new Uint8Array(cookedHeader.length + bytes.length);
  fullData.set(cookedHeader);
  fullData.set(bytes, cookedHeader.length);

  // 5. 生成时间戳
  const date = new Date(timestamp);
  const secs = Math.floor(date.getTime() / 1000);
  const usecs = (date.getTime() % 1000) * 1000;

  // 6. 构造PCAP报文头
  const packetHeader = new Uint8Array(16);
  const headerView = new DataView(packetHeader.buffer);
  headerView.setUint32(0, secs, true); // 时间戳秒
  headerView.setUint32(4, usecs, true); // 时间戳微秒
  headerView.setUint32(8, fullData.length, true); // 捕获长度
  headerView.setUint32(12, fullData.length, true); // 原始长度

  // 7. 合并所有数据
  const finalData = new Uint8Array(
    fileHeader.length + packetHeader.length + fullData.length
  );
  finalData.set(fileHeader);
  finalData.set(packetHeader, fileHeader.length);
  finalData.set(fullData, fileHeader.length + packetHeader.length);

  // 8. 文件Blob对象
  return new Blob([finalData], { type: 'application/octet-stream' });
}

// =========== WS ==============
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
  const { code, requestId, data } = res;
  if (code === RESULT_CODE_ERROR) {
    console.warn(res.msg);
    return;
  }

  // 建联时发送请求
  if (!requestId && data.clientId) {
    return;
  }

  // 订阅组信息
  if (!data?.groupId) {
    return;
  }
  if (data.groupId === `2_${traceId.value}`) {
    // 第一页降序时实时添加记录
    if (queryParams.pageNum === 1 && queryParams.sortOrder === 'desc') {
      tableState.data.unshift(data);
    }
    tablePagination.total += 1;
  }
}
/**建立WS连接 */
function fnWS() {
  const options: wsUtil.OptionsType = {
    url: '/ws',
    params: {
      /**订阅通道组
       *
       * 跟踪任务PCAP文件 (GroupID:2_traceId)
       */
      subGroupID: `2_${traceId.value}`,
    },
    onmessage: wsMessage,
    onerror: (ev: any) => {
      // 接收数据后回调
      console.error(ev);
    },
  };
  //建立连接
  ws.connect(options);
}

// =========== WK ==============
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };

type StateType = {
  /**初始化 */
  initialized: boolean;
  /**当前选中的帧编号 */
  selectedFrame: number;
  /**当前选中的帧数据 */
  packetFrame: { tree: any[]; data_sources: any[] };
  /**pcap包帧数据 */
  packetFrameTreeMap: Map<string, any> | null;
  /**当前选中的帧数据 */
  selectedTree: typeof NO_SELECTION;
  /**选择帧的Dump数据标签 */
  selectedDataSourceIndex: number;
};

const state = reactive<StateType>({
  initialized: false,
  selectedFrame: 0,
  /**当前选中的帧数据 */
  packetFrame: { tree: [], data_sources: [] },
  packetFrameTreeMap: null, // 注意：Map 需要额外处理
  selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
  /**选择帧的Dump数据标签 */
  selectedDataSourceIndex: 0,
});

/**解析帧数据为简单结构 */
function parseFrameTree(id: string, node: Record<string, any>) {
  let map = new Map();

  if (node.tree && node.tree.length > 0) {
    for (let i = 0; i < node.tree.length; i++) {
      const subMap = parseFrameTree(`${id}-${i}`, node.tree[i]);
      subMap.forEach((value, key) => {
        map.set(key, value);
      });
    }
  } else if (node.length > 0) {
    map.set(id, {
      id: id,
      idx: node.data_source_idx,
      start: node.start,
      length: node.length,
    });
  }

  return map;
}
/**报文数据点击选中 */
function handleSelectedFindSelection(src_idx: number, pos: number) {
  // console.log('fnSelectedFindSelection', pos);
  if (state.packetFrameTreeMap == null) return;
  // find the smallest one
  let current = null;
  for (let [k, pp] of state.packetFrameTreeMap) {
    if (pp.idx !== src_idx) continue;

    if (pos >= pp.start && pos <= pp.start + pp.length) {
      if (
        current != null &&
        state.packetFrameTreeMap.get(current).length > pp.length
      ) {
        current = k;
      } else {
        current = k;
      }
    }
  }
  if (current != null) {
    state.selectedTree = state.packetFrameTreeMap.get(current);
  }
}
/**帧数据点击选中 */
function handleSelectedTree(e: any) {
  state.selectedTree = e;
}

/**接收数据后回调 */
function wkMessage(res: Record<string, any>) {
  // console.log('wkMessage', res);
  switch (res.type) {
    case 'status':
      console.info(res.status);
      break;
    case 'error':
      console.warn(res.error);
      break;
    case 'init':
      wk.send({ type: 'columns' });
      state.initialized = true;
      fnGetList();
      break;
    case 'frames':
      const { frames } = res.data;

      // 有匹配的选择第一个
      if (frames.length > 0) {
        state.selectedFrame = frames[0].number;
        wk.send({ type: 'select', number: state.selectedFrame });
      }
      break;
    case 'selected':
      // 去掉两层
      res.data.tree.shift();
      res.data.tree.shift();
      state.packetFrame = res.data;
      state.packetFrameTreeMap = parseFrameTree('root', res.data);
      state.selectedTree = NO_SELECTION;
      state.selectedDataSourceIndex = 0;
      break;
    case 'processed':
      // setStatus(`Error: non-zero return code (${e.data.code})`);
      // 加载数据
      wk.send({
        type: 'frames',
        filter: '',
        skip: 0,
        limit: 1,
      });
      break;
    default:
      console.warn(res);
      break;
  }
}
/**建立WK连接 */
function fnWK() {
  const options: wkUtil.OptionsType = {
    url: wkUrl,
    onmessage: wkMessage,
    onerror: (ev: any) => {
      console.error(ev);
    },
  };
  //建立连接
  wk.connect(options);
}

onMounted(() => {
  // 初始字典数据
  Promise.allSettled([getDict('trace_msg_type'), getDict('trace_msg_direct')])
    .then(resArr => {
      if (resArr[0].status === 'fulfilled') {
        dict.traceMsgType = resArr[0].value;
      }
      if (resArr[1].status === 'fulfilled') {
        dict.traceMsgDirect = resArr[1].value;
      }
    })
    .finally(() => {
      fnWK();
      fnWS();
    });
});

onBeforeUnmount(() => {
  wk.send({ type: 'close' }) && wk.close();
  if (ws.state() <= WebSocket.OPEN) ws.close();
});
</script>

<template>
  <PageContainer>
    <a-card
      :bordered="false"
      :loading="!state.initialized"
      :body-style="{ padding: '12px' }"
    >
      <!-- 插槽-卡片左侧侧 -->
      <template #title>
        <a-space :size="8" align="center">
          <a-button type="default" @click.prevent="fnClose()">
            <template #icon><CloseOutlined /></template>
            {{ t('common.close') }}
          </a-button>
          <a-button
            type="primary"
            :loading="downLoading"
            @click.prevent="fnDownloadFile()"
          >
            <template #icon><DownloadOutlined /></template>
            {{ t('common.downloadText') }}
          </a-button>
          <span>
            {{ t('views.traceManage.task.traceId') }}:&nbsp;
            <strong>{{ traceId }}</strong>
          </span>
          <!-- 任务信息 -->
          <TaskInfoIcon :id="id" />
        </a-space>
      </template>

      <!-- 插槽-卡片右侧 -->
      <template #extra>
        <a-tooltip>
          <template #title>{{ t('common.reloadText') }}</template>
          <a-button type="text" @click.prevent="fnGetList()">
            <template #icon><ReloadOutlined /></template>
          </a-button>
        </a-tooltip>
      </template>

      <!-- 包数据表 -->
      <a-table
        class="table"
        row-key="id"
        :columns="tableColumns"
        :loading="tableState.loading"
        :data-source="tableState.data"
        :size="tableState.size"
        :pagination="tablePagination"
        :row-class-name="(record:any) => {
          if (record.id === tableState.row.id) {
            return 'table-striped-select';
          }
          return record.msgDirect === 0 ? 'table-striped-recv' : 'table-striped-send';
        }"
        :customRow="
          (record:any) => {
            return {
              onClick: () => fnVisible(record),
            };
          }
        "
        @change="fnTableChange"
        :scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 300px)' }"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'msgType'">
            <DictTag :options="dict.traceMsgType" :value="record.msgType" />
          </template>
          <template v-if="column.key === 'msgDirect'">
            <DictTag :options="dict.traceMsgDirect" :value="record.msgDirect" />
          </template>
        </template>
      </a-table>
      <!-- 帧数据 -->
      <a-row
        :gutter="16"
        style="border: 2px rgb(217 217 217) solid; border-radius: 6px"
        v-show="state.selectedFrame == 1"
      >
        <a-col :lg="12" :md="12" :xs="24" class="tree">
          <!-- 帧数据 -->
          <DissectionTree
            id="root"
            :select="handleSelectedTree"
            :selected="state.selectedTree"
            :tree="state.packetFrame.tree"
          />
        </a-col>
        <a-col :lg="12" :md="12" :xs="24" class="dump">
          <!-- 报文数据 -->
          <a-tabs
            v-model:activeKey="state.selectedDataSourceIndex"
            :tab-bar-gutter="16"
            :tab-bar-style="{ marginBottom: '8px' }"
          >
            <a-tab-pane
              :key="idx"
              :tab="v.name"
              v-for="(v, idx) in state.packetFrame.data_sources"
              style="overflow: auto"
            >
              <DissectionDump
                :base64="v.data"
                :select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
                :selected="
                  idx === state.selectedTree.idx
                    ? state.selectedTree
                    : NO_SELECTION
                "
              />
            </a-tab-pane>
          </a-tabs>
        </a-col>
      </a-row>
    </a-card>
  </PageContainer>
</template>

<style scoped>
.table :deep(.ant-pagination) {
  padding: 0 24px;
}
.table :deep(.table-striped-select) td {
  background-color: #c2c2c2;
  cursor: pointer;
}
.table :deep(.table-striped-recv) td {
  background-color: #a9e2ff;
  cursor: pointer;
}
.table :deep(.table-striped-send) td {
  background-color: #bcfbb3;
  cursor: pointer;
}

.tree {
  font-size: 0.8125rem;
  line-height: 1.5rem;
  padding-bottom: 0.75rem;
  padding-top: 0.75rem;
  white-space: nowrap;
  overflow-y: auto;
  user-select: none;
  height: 100%;
}
.tree > ul.tree {
  min-height: 15rem;
}

.dump {
  padding-bottom: 0.75rem;
  padding-top: 0.75rem;
  overflow-y: auto;
  height: 100%;
}
.dump .ant-tabs-tabpane {
  min-height: calc(15rem - 56px);
}
</style>
