<script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/es';
import DissectionTree from '../tshark/components/DissectionTree.vue';
import DissectionDump from '../tshark/components/DissectionDump.vue';
import {
  RESULT_CODE_ERROR,
  RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
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 saveAs from 'file-saver';
import {
  packetDevices,
  packetStart,
  packetStop,
  packetFilter,
  packetKeep,
  packetPCAPFile,
} from '@/api/trace/packet';
import { parseDateToStr } from '@/utils/date-utils';
import { ColumnsType } from 'ant-design-vue/es/table';
const ws = new wsUtil.WS();
const wk = new wkUtil.WK();
const { t } = useI18n();

// =========== 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;
      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':
      // 首行修改帧编号
      const fristFrame = res.data.tree[0];
      res.data.tree[0].label = fristFrame.label.replace(
        'Frame 1:',
        `Frame ${tableState.selectedNumber}:`
      );
      const item = fristFrame.tree.find(
        (item: any) => item.label === 'Frame Number: 1'
      );
      if (item) {
        item.label = `Frame Number:  ${tableState.selectedNumber}:`;
      }

      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);
}

// =========== 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) {
    tableState.data = [];
    tableState.total = 0;
    taskState.keepTimer = setInterval(() => {
      if (ws.state() != WebSocket.OPEN) {
        clearInterval(taskState.keepTimer);
        return;
      }
      packetKeep(taskState.task.taskNo, 120);
    }, 90 * 1000);
    return;
  }

  // 订阅组信息
  if (!data?.groupId) {
    return;
  }
  if (data.groupId === `4_${taskState.task.taskNo}`) {
    const packetData = data.data;
    tableState.total = packetData.number;
    tableState.data.unshift(packetData);
    if (tableState.data.length > 100) {
      tableState.data.pop();
    }
  }
}
/**建立WS连接 */
function fnWS() {
  const options: wsUtil.OptionsType = {
    url: '/ws',
    params: {
      /**订阅通道组
       *
       * 信令跟踪Packet (GroupID:4_taskNo)
       */
      subGroupID: `4_${taskState.task.taskNo}`,
    },
    heartTimer: 30 * 1000,
    onmessage: wsMessage,
    onerror: (ev: any) => {
      // 接收数据后回调
      console.error(ev);
    },
  };
  //建立连接
  ws.connect(options);
}

// =========== 任务 ==============
type TaskStateType = {
  /**网卡设备列表 */
  devices: { id: string; label: string; children: any[] }[];

  /**任务 */
  task: {
    taskNo: string;
    device: string;
    filter: string;
    outputPCAP: boolean;
  };

  /**过滤条件 */
  filter: string;
  /**过滤条件错误信息 */
  filterError: string | null;

  /**保活调度器 */
  keepTimer: any;

  /**停止标记 */
  stop: boolean;
  stopOutputPCAP: boolean;
};

const taskState = reactive<TaskStateType>({
  devices: [],

  task: {
    taskNo: '',
    device: '192.168.5.58',
    filter: '',
    outputPCAP: false,
  },

  filter: '',
  filterError: null,

  keepTimer: null,

  stop: false,
  stopOutputPCAP: false,
});

/**开始跟踪 */
function fnStart() {
  if (taskState.keepTimer) return;
  taskState.keepTimer = true;
  const hide = message.loading(t('common.loading'), 0);
  // taskState.task.taskNo = 'laYlTbq';
  taskState.task.taskNo = Number(Date.now()).toString(16);
  taskState.task.filter = taskState.filter;
  packetStart(toRaw(taskState.task))
    .then(res => {
      if (res.code === RESULT_CODE_SUCCESS) {
        // 清空选择帧的数据
        state.selectedFrame = 0;
        state.packetFrame = { tree: [], data_sources: [] };
        state.packetFrameTreeMap = null;
        state.selectedTree = NO_SELECTION;
        state.selectedDataSourceIndex = 0;
        // 开启
        if (!state.initialized) {
          fnWK();
        }
        clearInterval(taskState.keepTimer);
        taskState.keepTimer = null;
        fnWS();
        // 记录状态
        taskState.stop = false;
        taskState.stopOutputPCAP = taskState.task.outputPCAP;
      } else {
        message.error(t('common.operateErr'), 3);
      }
    })
    .finally(() => {
      hide();
    });
}

/**停止跟踪 */
function fnStop() {
  if (typeof taskState.keepTimer !== 'number') return;
  const hide = message.loading(t('common.loading'), 0);
  packetStop(taskState.task.taskNo)
    .then(res => {
      if (res.code !== RESULT_CODE_SUCCESS) {
        message.warning(res.msg, 3);
      }
      // 关闭监听
      ws.close();
      clearInterval(taskState.keepTimer);
      taskState.keepTimer = null;

      taskState.filter = '';
      taskState.filterError = null;

      taskState.stop = true;
    })
    .finally(() => {
      hide();
    });
}

/**跟踪数据表过滤 */
function handleFilterFrames() {
  packetFilter(taskState.task.taskNo, taskState.filter).then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      taskState.task.filter = taskState.filter;
      taskState.filterError = null;
    } else {
      taskState.filterError = res.msg;
    }
  });
}

/**选择网卡 */
function fnDevice(v: string) {
  taskState.task.device = v;
}

/**下载触发等待 */
let downLoading = ref<boolean>(false);
/**信息文件下载 */
function fnDownloadPCAP() {
  if (downLoading.value) return;
  const fileName = `packet_${taskState.task.taskNo}.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);
      packetPCAPFile(taskState.task.taskNo)
        .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;
  /**记录数据 */
  data: Record<string, any>[];
  /**总记录数 */
  total: number;
  /**选择帧编号 */
  selectedNumber: number;
};

/**表格状态 */
let tableState: TabeStateType = reactive({
  loading: false,
  data: [],
  total: 0,
  selectedNumber: 0,
});

/**表格字段列 */
let tableColumns = ref<ColumnsType>([
  {
    title: 'Number',
    dataIndex: 'number',
    align: 'left',
    width: 100,
  },
  {
    title: 'Time',
    dataIndex: 'time',
    align: 'left',
    width: 150,
    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,
        'HH:mm:ss.SSS'
      )}.${nanosecondsPart}`;
    },
  },
  {
    title: 'Source',
    dataIndex: 'source',
    align: 'left',
    width: 150,
    ellipsis: true,
  },
  {
    title: 'Destination',
    dataIndex: 'destination',
    align: 'left',
    width: 150,
    ellipsis: true,
  },
  {
    title: 'Protocol',
    dataIndex: 'protocol',
    key: 'protocol',
    align: 'left',
    width: 100,
  },
  {
    title: 'length',
    dataIndex: 'length',
    align: 'right',
    width: 100,
  },
  {
    title: 'Info',
    dataIndex: 'info',
    align: 'left',
    ellipsis: true,
  },
]);

/**
 * 查看帧数据
 * @param row 记录信息
 */
function fnVisible(row: Record<string, any>) {
  // 选中行重复点击时显示隐藏
  if (row.id === tableState.selectedNumber && state.selectedFrame !== 0) {
    state.selectedFrame = 0;
    return;
  }
  tableState.selectedNumber = row.number;
  const blob = generatePCAP(row.time / 1e3, row.data);
  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, // version_major (2)
    0x04,
    0x00, // version_minor (4)
    0x00,
    0x00,
    0x00,
    0x00, // thiszone (UTC)
    0x00,
    0x00,
    0x00,
    0x00, // sigfigs (固定0)
    0xff,
    0xff,
    0x00,
    0x00, // snaplen (最大捕获长度)
    0x01,
    0x00,
    0x00,
    0x00, // network (LINKTYPE_ETHERNET)
  ]);

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

  // 4. 构造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, bytes.length, true); // 捕获长度
  headerView.setUint32(12, bytes.length, true); // 原始长度

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

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

onMounted(() => {
  packetDevices().then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      taskState.devices = res.data;
      if (res.data.length === 0) return;
      taskState.task.device = res.data[0].id;
    }
  });
  fnWK();
});

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

<template>
  <PageContainer>
    <a-card :bordered="false" :body-style="{ padding: '12px' }">
      <!-- 插槽-卡片左侧侧 -->
      <template #title>
        <a-space :size="8" align="end">
          <a-dropdown-button
            type="primary"
            @click="fnStart"
            :disabled="!taskState.stop && taskState.task.taskNo !== ''"
          >
            <PlayCircleOutlined />
            Start Trace
            <template #overlay>
              <a-menu
                @click="({ key }:any) => fnDevice(key)"
                :selectedKeys="[taskState.task.device]"
              >
                <a-menu-item v-for="v in taskState.devices" :key="v.id">
                  <a-popover placement="rightTop" trigger="hover">
                    <template #content>
                      <div v-for="c in v.children">{{ c.id }}</div>
                    </template>
                    <template #title>
                      <span>IP Address</span>
                    </template>
                    <div>{{ v.label }}</div>
                  </a-popover>
                </a-menu-item>
              </a-menu>
            </template>
            <template #icon><DownOutlined /></template>
          </a-dropdown-button>

          <a-button
            danger
            @click.prevent="fnStop()"
            :disabled="taskState.stop || taskState.task.taskNo === ''"
          >
            <template #icon><CloseCircleOutlined /></template>
            Stop Trace
          </a-button>

          <a-checkbox
            v-model:checked="taskState.task.outputPCAP"
            :disabled="!taskState.stop && taskState.task.taskNo !== ''"
          >
            Output PCAP
          </a-checkbox>

          <a-tag color="processing" v-if="taskState.task.filter !== ''">
            {{ taskState.task.filter }}
          </a-tag>
        </a-space>
      </template>

      <!-- 插槽-卡片右侧 -->
      <template #extra>
        <a-space :size="8" align="end">
          <a-button
            type="primary"
            :loading="downLoading"
            @click.prevent="fnDownloadPCAP()"
            v-if="taskState.stop && taskState.stopOutputPCAP"
          >
            <template #icon><DownloadOutlined /></template>
            {{ t('common.downloadText') }}
          </a-button>
          <span>
            Packets:
            <strong>{{ tableState.total }}</strong>
          </span>
          <span>
            Task No:
            <strong>{{ taskState.task.taskNo }}</strong>
          </span>
        </a-space>
      </template>

      <!-- 包数据表过滤 -->
      <a-input-group compact>
        <a-auto-complete
          v-model:value="taskState.filter"
          :options="[
            { value: 'tcp and port 33030 and greater 100' },
            {
              value:
                '(src 192.168.5.58 and dst port 33030) or (src 192.168.9.59 and dst port 33030)',
            },
            { value: 'src host 192.168.5.58 and dst host 192.168.5.58' },
            { value: 'host 192.168.5.58 and greater 100 and less 2500' },
          ]"
          style="width: calc(100% - 100px)"
          :allow-clear="true"
        >
          <a-input
            placeholder="BPF Basic Filter, example: tcp"
            @pressEnter="handleFilterFrames"
          >
            <template #prefix>
              <FilterOutlined />
            </template>
          </a-input>
        </a-auto-complete>
        <a-button
          type="primary"
          html-type="submit"
          style="width: 100px"
          @click="handleFilterFrames"
          :disabled="taskState.task.taskNo === '' || taskState.stop"
        >
          Filter
        </a-button>
      </a-input-group>
      <a-alert
        :message="taskState.filterError"
        type="error"
        v-if="taskState.filterError != null"
      />

      <!-- 包数据表 -->
      <a-table
        class="table"
        row-key="number"
        :columns="tableColumns"
        :loading="tableState.loading"
        :data-source="tableState.data"
        size="small"
        :pagination="false"
        :row-class-name="(record:any) => { 
          return `table-striped-${record.protocol}`
        }"
        :customRow="
          (record:any) => {
            return {
              onClick: () => fnVisible(record),
            };
          }
        "
        :scroll="{ x: tableColumns.length * 150, y: '400px' }"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'protocol'">
            <!-- <DictTag :options="dict.traceMsgType" :value="record.msgType" /> -->
          </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>
.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>
