<template>
  <el-dialog :title="title" v-model="dialogFormVisible" @closed="handleClosed">
    <el-form :model="form" ref="formRef">
      <el-form-item label="任务名称" prop="name" :label-width="formLabelWidth" required>
        <el-input v-model="form.name" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="任务状态" :label-width="formLabelWidth">
        <el-select v-model="form.status" placeholder="请选择任务状态">
          <el-option label="排队" value="queue"></el-option>
          <el-option label="立即执行" value="immediately"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="文件" :label-width="formLabelWidth">
        <el-upload ref="upload" :action="'/api/file/upload/pdf/' + form.job_id" :mutiple="false" :limit="100"
          :on-preview="handlePreview" :on-remove="handleRemove" :on-success="handleSuccess" v-model:file-list="fileList"
          :on-exceed="handleExceed" :auto-upload="false">
          <el-button @click.stop="knowledgeBase.visible = true" size="small" type="primary">知识库导入</el-button>
          <el-button slot="trigger" size="small" type="primary">上传文件</el-button>

          <div slot="tip" class="el-upload__tip">只能上传pdf文件，且不超过500kb</div>
        </el-upload>
      </el-form-item>
    </el-form>
    <div slot="footer">
      <el-button type="primary" @click="handleConfirm">确 定</el-button>
      <el-button @click="dialogFormVisible = false">取 消</el-button>
    </div>
    <div class="knowledge-base" v-show="knowledgeBase.visible">
      <div class="topbar"><span @click="knowledgeBase.visible = false" class="close-knowledge-base"><el-icon>
            <Close />
          </el-icon></span>
      </div>
      <div class="knowledge-base-content">
        <el-scrollbar class="knowledge-base-list">
          <div class="knowledge-base-item">
            <span class="name">知识库名称</span>
            <span class="number">文件数量</span>
          </div>
          <div :class="knowledgeBase.activeId === item.id ? 'knowledge-base-item active' : 'knowledge-base-item'"
            v-for="item in knowledgeBase.data" :key="item.id" @click="handleKnowledgeBaseClick(toRaw(item))">
            <span class="name">
              <span class="icon">
                <span class="document-icon"></span>
              </span>
              <span class="text">
                <div class="name-text">{{ item.name }}</div>
                <div class="description-text">{{ item.description }}</div>
              </span>
            </span>
            <span class="number">{{ item.file_count }}</span>
          </div>
        </el-scrollbar>
        <div class="knowledge-base-detail">
          <div class="management-content">
            <div class="management-content-top">
              <span class="search">
                <el-input v-model="knowledgeBase.querySearch" size="large" placeholder="搜索"
                  @keydown.enter="debounceGetKBfileList" :prefix-icon="Search" />
              </span>
              <span class="add-file" @click="handleSelectedImport()">
                <span class="text">批量导入</span>
              </span>
            </div>
            <el-scrollbar class="management-content-middle">
              <el-table :data="knowledgeBase.filesList" ref="KBTableRef">
                <el-table-column type="selection" width="30" />
                <el-table-column label="#" prop="index" width="50" />
                <el-table-column prop="file_name" min-width="150" label="标题">
                  <template #default="{ row }">
                    <div class="list-name">
                      <span class="icon-area">
                        <i :class="`${row.file_type}-icon`"></i>
                      </span>
                      <span class="text-area">
                        {{ row.file_name }}
                      </span>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column prop="knowledge_type" label="知识类型" />
                <el-table-column prop="version" label="版本" />
                <el-table-column prop="author" label="作者（主编）" />
                <el-table-column prop="year" label="年份" />
                <el-table-column prop="creator" label="上传人" />
                <el-table-column prop="created_at" label="上传时间" width="150" />
                <el-table-column prop="" label="状态" width="80">
                  <template #default="{ row }">
                    <span v-if="row.isValid">
                      <i class="circle" type="success"></i>
                      <el-text type="success">可用</el-text>
                    </span>
                    <span v-else>
                      <i class="circle" type="warning"></i>
                      <el-text type="warning">异常</el-text>
                    </span>
                  </template>
                </el-table-column>
                <el-table-column fixed="right" label="操作" min-width="55">
                  <template #default="{ row }">
                    <div class="operation">
                      <el-button link type="primary" @click="handleImportFiles([toRaw(row)])">导入</el-button>
                    </div>
                  </template>
                </el-table-column>
              </el-table>
              <div class="pagination"
                v-if="totalPage > 1 || paginationData.defaultPageSize !== paginationData.currentPageSize">
                <el-pagination :current-page="paginationData.currentPage" :page-sizes="paginationData.pageSizes"
                  :disabled="paginationData.disabled" :default-page-size="paginationData.defaultPageSize"
                  layout="total, sizes, prev, pager, next, jumper" :total="paginationData.total"
                  @size-change="handleSizeChange" @current-change="handleCurrentChange" />
              </div>
            </el-scrollbar>

          </div>
        </div>

      </div>

    </div>
  </el-dialog>

</template>

<script setup lang="ts">
import { ref, watch, toRaw, onMounted, computed, getCurrentInstance } from 'vue'
import { Search } from '@element-plus/icons-vue'
import CryptoJS from 'crypto-js';
import encHex from 'crypto-js/enc-hex'
import { createJob, putQueueJob, getKnowledgeBase, getKnowledgeBaseFilesList } from '@/api/AgentApi'
import axios from 'axios'
import { genFileId, ElMessage } from 'element-plus'
import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'
import { throttle, debounce } from 'lodash'
const KBTableRef = ref()
const formRef = ref()
const dialogFormVisible = ref(false)
const formLabelWidth = ref('120px')

const paginationData = ref<any>({
  currentPage: 1,
  pageSizes: [10, 50, 100, 200],
  disabled: false,
  total: 0,
  defaultPageSize: 10,
  currentPageSize: 10
})
const totalPage = computed(() => {
  return Math.ceil(paginationData.value.total / paginationData.value.currentPageSize)
})
const handleSizeChange = (pageSize: number) => {
  paginationData.value.currentPageSize = pageSize
  debounceGetKBfileList()
}

const handleCurrentChange = (page: number) => {
  paginationData.value.currentPage = page
  debounceGetKBfileList()
}
type kbData = {
  id: number,
  name: string | null,
  description: string | null,
  tags: string | null,
  file_count: number,
  created_at: string | null,
  updated_at: string | null
}
type knowledgeBaseType = {
  visible: boolean,
  activeId: number | string,
  querySearch: string,
  data: kbData[],
  filesList: any[]
}
let knowledgeBase = ref<knowledgeBaseType>({
  visible: false,
  activeId: 0,
  querySearch: "",
  data: [],
  filesList: []
})
function handleKnowledgeBaseClick(data: kbData) {
  knowledgeBase.value.activeId = data.id
  paginationData.value.currentPage = 1
}
function handleGetKBfileList() {
  getKnowledgeBaseFilesList({
    file_name: knowledgeBase.value.querySearch,
    pageSize: paginationData.value.currentPageSize,
    pageNo: paginationData.value.currentPage,
    kbId: knowledgeBase.value.activeId
  }).then((res: any) => {
    const { data, code, message } = res
    if (code === 200) {
      knowledgeBase.value.filesList = data.list
      for (let i = 0; i < knowledgeBase.value.filesList.length; i++) {
        knowledgeBase.value.filesList[i].index = (paginationData.value.currentPage - 1) * paginationData.value.currentPageSize + i + 1
        knowledgeBase.value.filesList[i].isValid = true
        checkLinkValidity(i, knowledgeBase.value.filesList[i].minio_url)
      }
      paginationData.value.total = data.total
    }
  }).catch((e: any) => {
    console.log(e)
  })
}
const throttleGetKBfileList = throttle(handleGetKBfileList, 1000)
const debounceGetKBfileList = debounce(handleGetKBfileList, 500, { leading: true })
const upload = ref()
const form = ref({
  job_id: 0,
  job_category: '',
  job_name: '',
  job_details: '',
  job_creator: '',
  queue_category: '',
  queue_name: '',
  name: '',
  status: 'queue'
})
const fileList = ref<any>([])
// 记录已导入文件的哈希值
const importedFileHashes = ref<string[]>([])

const props = defineProps({
  title: { type: String, required: true, default: '工作' },
  queue_category: { type: String, required: true, default: 'SYSTEM' },
  queue_name: { type: String, required: true, default: 'DEFAULT' },
  job_category: { type: String, required: false, default: 'USER' },
  job_name: { type: String, required: false, default: '工作名称' },
  job_details: { type: String, required: false, default: '' },
  job_creator: { type: String, required: false, default: 'admin' },
  status: { type: Number, required: false, default: 0 },
})
const emit = defineEmits(['update:modelValue', 'success', 'cancel','closed'])
const showDialog = (visible: boolean = true) => {
  // console.log("OCRDialog showDialog")
  if (upload.value) {
    upload.value.clearFiles();
  }
  dialogFormVisible.value = visible
}

// 清除上传文件列表
const clearFileList = () => {
    fileList.value = []
    if (upload.value) {
        upload.value.clearFiles()
    }
    importedFileHashes.value = [] // 清空已导入文件的哈希值记录
}
function handleGetKnowledgeBase() {
  getKnowledgeBase().then((response: any) => {
    // console.log('getKnowledgeBase', response)
    const { data, code, message } = response
    if (code === 200) {
      knowledgeBase.value.data = data.list || []
    }
  }).catch((e: any) => {
    console.log(e)
  })
}
function fetchFile(fileName: string, fileUrl: string) {
  axios.get(fileUrl, { responseType: 'blob' })
    .then(async response => {
      // upload.value!.clearFiles()
      // 创建文件对象并添加到文件列表
      const rawFile = new File([response.data], fileName, { type: response.data.type });
      const file = rawFile as UploadRawFile
      file.uid = genFileId()
      // const fileHashHex = await calculateFileHashForCryptoJS(file)
      // const { md5, sha256 } = await hashFile(file)
      let fileExist = false;
      // await Promise.allSettled(fileList.value.map(async (it: any) => {
      //   const itFileHash = await hashFile(it.raw);
      //   if (sha256 === itFileHash.sha256) {
      //     fileExist = true;
      //     return true;
      //   }
      //   return false;
      // }))
      if (fileExist) {
        ElMessage({
          message: `文件“${fileName}”已存在上传列表中`,
          type: 'info',
          plain: true,
        })
      } else {
        upload.value!.handleStart(file)
        ElMessage({
          message: `文件“${fileName}”导入成功`,
          type: 'success',
          plain: true,
        })
      }
      // knowledgeBase.value.visible = false
    })
    .catch(error => {
      ElMessage({
        message: `从知识库获取文件“${fileName}”失败`,
        type: 'error'
      })
      console.log(error);
    });
}
function handleSelectedImport() {
  const SelectionRows = KBTableRef.value.getSelectionRows()
  handleImportFiles(toRaw(SelectionRows))
  knowledgeBase.value.visible = false
  // console.log(SelectionRows)
}
// 使用原生方法计算文件的hash值,该方法兼容性较差
async function calculateFileHashForNative(file: File) {
  const arrayBuffer = await file.arrayBuffer(); // 将文件读取为 ArrayBuffer
  const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); // 计算哈希值
  const hashArray = Array.from(new Uint8Array(hashBuffer)); // 将哈希转换为字节数组
  const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); // 转换为十六进制字符串
  // console.log("calculateFileHashForNative1:", hashHex)
  return hashHex;
}

// 使用crypto-js计算文件的hash值 ,该方法易发生卡顿
async function calculateFileHashForCryptoJS(file: File) {
  return new Promise((resolve, reject) => {
    try {
      const reader = new FileReader();
      reader.readAsArrayBuffer(file)
      reader.onload = function (e) {
        const arrayBuffer = e.target?.result;  // 读取的文件内容
        // 将 ArrayBuffer 转换为 CryptoJS 可以处理的 WordArray
        const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
        // 计算文件的 SHA-256 哈希值并转为 Base64 编码字符串
        const hashBase64 = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Base64);
        // 计算文件的 SHA-256 哈希值并转为 16 进制字符串
        const hashHex = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.hex);
        // console.log('calculateFileHashForCryptoJS:', hashHex);
        resolve(hashHex)
      };

      reader.onerror = () => {
        reject('')
      }
    } catch (e) {
      console.log(e)
      reject('')
    }
  })
}


// function handleImportFiles(filesList: any[]) {
//   for (let i = 0; i < filesList.length; i++) {
//     // if (i > 0) break;
//     fetchFile(filesList[i].file_name, filesList[i].minio_url)
//   }
// }

/**
 * 用于计算文件的hash值，包括sha256值和md5值
 */
function hashFile(file: any) {
  /**
   * 使用指定的算法计算hash值
   */
  function hashFileInternal(file: any, alog: any) {
    // 指定块的大小，这里设置为20MB,可以根据实际情况进行配置
    const chunkSize = 20 * 1024 * 1024
    let promise: any = Promise.resolve()
    // 使用promise来串联hash计算的顺序。因为FileReader是在事件中处理文件内容的，必须要通过某种机制来保证update的顺序是文件正确的顺序
    for (let index = 0; index < file.size; index += chunkSize) {
      promise = promise.then(() => hashBlob(file.slice(index, index + chunkSize)))
    }

    /**
     * 更新文件块的hash值
     */
    function hashBlob(blob: any) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onload = ({ target }) => {
          const wordArray = CryptoJS.lib.WordArray.create(target?.result)
          // 增量更新计算结果
          alog.update(wordArray)
          resolve(true)
        }
        reader.readAsArrayBuffer(blob)
      })
    }

    // 使用promise返回最终的计算结果
    return promise.then(() => encHex.stringify(alog.finalize()))
  }

  // 同时计算文件的sha256和md5,并使用promise返回
  return Promise.all([hashFileInternal(file, CryptoJS.algo.SHA256.create()),
  hashFileInternal(file, CryptoJS.algo.MD5.create())])
    .then(([sha256, md5]) => ({
      sha256,
      md5
    }))
}

const handlePreview = (file: any) => {
  console.log(file)
}

const handleExceed: UploadProps['onExceed'] = (files) => {
  upload.value!.clearFiles()
  const file = files[0] as UploadRawFile
  // console.log(handleExceed, file)
  file.uid = genFileId()
  upload.value!.handleStart(file)
}

const handleRemove = (file: any, fileList: any) => {
  console.log(file, fileList)
}

async function calculateFileHash(file: File) {
  const arrayBuffer = await file.arrayBuffer();
  const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
  return hashHex;
}

function handleImportFiles(filesList: any[]) {
  filesList.forEach(async (fileInfo) => {
    const { file_name, minio_url } = fileInfo;
    try {
      const response = await axios.get(minio_url, { responseType: 'blob' });
      const rawFile = new File([response.data], file_name, { type: response.data.type });
      const fileHash = await calculateFileHash(rawFile);

      if (importedFileHashes.value.includes(fileHash)) {
        ElMessage({
          message: `文件“${file_name}”已导入过，不能重复导入`,
          type: 'info',
          plain: true,
        });
        return;
      }

      const file = rawFile as UploadRawFile;
      file.uid = genFileId();
      upload.value!.handleStart(file);
      importedFileHashes.value.push(fileHash);
      ElMessage({
        message: `文件“${file_name}”导入成功`,
        type: 'success',
        plain: true,
      });
    } catch (error) {
      ElMessage({
        message: `从知识库获取文件“${file_name}”失败`,
        type: 'error'
      });
      console.log(error);
    }
  });
}

const handleSuccess = (response: any, file: any, fileList: any) => {
  console.log(response, file, fileList);
  const fileHash = importedFileHashes.value.find(hash => {
    // 这里简单通过文件名匹配，实际可根据需求优化
    return file.name === file.raw.name;
  });
  if (fileHash) {
    // 上传成功后保留哈希记录
    importedFileHashes.value.push(fileHash);
  }
  emit('success');
}

const handleConfirm = () => {
  formRef.value.validate((valid: any) => {
    if (valid) {
      // console.log('submit', props)
      form.value.job_category = props.queue_category + "_" + props.queue_name;
      form.value.job_name = form.value.name;
      form.value.queue_category = props.queue_category;
      form.value.queue_name = props.queue_name;
      form.value.job_details = props.job_details;
      form.value.job_creator = props.job_creator;

      // console.log('form values ', form.value)
      createJob(form.value).then((res: any) => {
        var job = res.records[0]
        form.value.job_id = job.id;
        upload.value.submit();
      })
    }
  })
}
defineExpose({ showDialog,clearFileList })

function handleClosed() {
  knowledgeBase.value.visible = false
  clearFileList ()
}

watch(() => knowledgeBase.value.activeId, (newValue) => {
  debounceGetKBfileList()
  // console.log('activeId', newValue)
})
watch(() =>knowledgeBase.value.visible, (newValue) => {
  if (!newValue) {
    KBTableRef.value.clearSelection() // 清除选择
  }
})

async function checkLinkValidity(index: number, url: string) {
  try {
    const response = await axios.head(url);  // 使用 HEAD 请求，仅请求响应头
    if (response.status >= 200 && response.status < 300) {
      knowledgeBase.value.filesList[index].isValid = true
    } else {
      knowledgeBase.value.filesList[index].isValid = false  // 返回 false 表示链接无效
    }
  } catch (error) {
    knowledgeBase.value.filesList[index].isValid = false // 请求失败，返回 false
  }
}

onMounted(() => {
  handleGetKnowledgeBase()
})

</script>

<style lang="less" scoped>
.knowledge-base {
  position: fixed;
  top: 50%;
  left: 50%;
  width: 80vw;
  height: 90vh;
  background-color: white;
  display: flex;
  flex-direction: column;
  // overflow: auto;
  transform: translate(-50%, -50%);
  border-radius: 5px;
  min-width: 800px;

  .topbar {
    padding: 5px;
    flex: 0 0 auto;

    .close-knowledge-base {
      margin-left: calc(100% - 24px);
      cursor: pointer;

    }
  }

  .knowledge-base-content {
    flex: 1 1 auto;
    // overflow: auto;
    display: flex;
    flex-wrap: nowrap;
    min-height: 0;

    .knowledge-base-list {
      flex: 0 0 auto;
      width: 300px;
      // background-color: skyblue;
      border-right: 1px solid #E4E4E4;

      .knowledge-base-item {
        display: flex;
        border-bottom: 1px solid #FAFAFA;
        padding: 10px 5px;
        gap: 0px 20px;
        align-items: center;

        &:first-child {
          background-color: #F2F5F9;
          padding: 10px 5px;
          // font-size: 17px;
        }

        &:hover,
        &:is(.active) {
          background-color: #F2F5F9;
        }

        .name {
          flex: 1 1 auto;
          display: flex;
          flex-wrap: nowrap;
          align-items: center;
          min-width: 0px;

          .icon {
            flex: 0 0 auto;
            padding: 2px;
            background-color: #7733FF;
            border-radius: 50%;
            display: inline-block;
            margin-right: 10px;
          }

          .text {
            flex: 1 1 auto;
            white-space: nowrap;
            text-overflow: ellipsis;
            overflow: hidden;
            width: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;

            &>div {
              width: 100%;
              white-space: nowrap;
              text-overflow: ellipsis;
              overflow: hidden;
              display: inline-block;
              line-height: 1.4;
            }

            .name-text {
              font-weight: bold;
              font-size: 16px;
            }

            .description-text {
              font-size: 14px;
              color: #D8D8D8;
            }
          }
        }

        .number {
          flex: 0 0 70px;
        }
      }

    }

    .knowledge-base-detail {
      flex: 1 1 auto;
      min-width: 0px;
      overflow: auto;
      min-width: 400px;
      // background-color: orange;
    }
  }

  .management-content {
    flex: 1 1 auto;
    min-width: 0px; //让盒子可以压缩
    min-height: 0px;
    padding: 0px 5px;
    display: flex;
    height: 100%;
    flex-direction: column;

    .management-content-top {
      clear: both;
      flex: 0 0 auto;
      position: relative;

      .add-file {
        position: absolute;
        right: 10px;
        bottom: 0px;
        transform: translateY(-25%);
        color: white;
        // width: 100px;
        background-color: #169BD5;
        margin-left: auto;
        padding: 10px 25px;
        border-radius: 3px;
        display: inline-block;
        cursor: pointer;
        // float: right;
        // display: flex;

        // .el-icon,
        .text {
          // vertical-align: middle;
          // margin-left: 10px;
        }
      }
    }

    .management-content-middle {
      flex: 1 1 auto;
      // overflow: auto;
      padding-right: 10px;

      /deep/ .el-scrollbar {
        .el-table__header-wrapper {
          position: sticky;
          top: 0;
        }
      }

    }

    .pagination {
      flex: 0 0 auto;
    }
  }

  &::v-deep .el-table {
    min-width: 0px;
    max-width: 100%;

    .document-download-icon {
      width: 28px;
      height: 28px;
      cursor: pointer;
    }

    .list-name {
      white-space: nowrap;
      text-overflow: ellipsis;
      display: inline;
    }

    .circle {
      border-radius: 50%;
      width: 10px;
      height: 10px;
      background-color: #8AD068;
      display: inline-block;
      margin-right: 10px;

      &:is([type='warning']) {
        background-color: #E6A23C;
      }
    }

    .operation {
      display: flex;
      align-items: center;
      justify-content: space-evenly;

      .el-switch {
        &:is(.is-checked) {
          .el-switch__core {
            background-color: #155AEF;
          }
        }

        .el-switch__core {
          border-radius: 5px;
          width: 35px;
          min-width: 0px;
          // background-color: #155AEF;
        }

        .el-switch__action {
          border-radius: 3px;
        }
      }
    }
  }

  .search {
    // padding: 10px 20px;
    margin: 5px;
    background: white;
    display: inline-block;

    /deep/ .el-input__wrapper {
      background-color: #EFF1F5;
      border-radius: 10px;
    }

    .el-input {
      width: 250px;
    }
  }

  .pagination {
    // text-align: right;
    // background-color: @bgColor;
    display: flex;
    padding: 0px 5px;

    /deep/.el-pagination {
      background-color: #fff;
      // justify-content: end;
      margin-left: auto;
      padding: 10px 0px 20px;
      // margin: 0px 20px;

      .page-box {
        border: 1px solid #E7E8EE;
        border-radius: 3px;
        box-sizing: border-box;
      }

      .el-pager {
        .is-active {
          color: #fff;
          background-color: #5780FC;
        }

        .number {
          .page-box();
          margin-left: 10px;

          &:last-child {
            margin-right: 10px;
          }
        }
      }

      .btn-next,
      .btn-quicknext,
      .btn-quickprev,
      .btn-prev {
        .page-box()
      }

      .btn-quicknext,
      .btn-quickprev {
        margin-left: 10px;
      }
    }

  }

  .document-icon {
    width: 32px;
    height: 32px;
  }
}
</style>