<template>
  <div ref="mod">
    <a-modal
      :get-container="() => $refs.mod"
      :open="open"
      :mask-closable="false"
      :width="900"
      @cancel="cancel"
      :footer="null"
    >
      <template #title>
        <div class="header">
          <div class="title">{{ title }}</div>
        </div>
      </template>

      <a-upload-dragger
        v-model:fileList="fileList"
        name="file"
        :multiple="false"
        :action="action"
        :accept="acceptType"
        :headers="headers"
        :customRequest="customRequest"
        :before-upload="beforeUpload"
        @change="handleChange"
        @drop="handleDrop"
      >
        <p class="ant-upload-drag-icon"><inbox-outlined /></p>
        <p class="ant-upload-text">单击或拖动文件到此区域进行上传</p>
        <p class="ant-upload-hint">支持 {{ allowedExt.join(', ') }} 格式文件，最大 10MB </p>
      </a-upload-dragger>

      <div class="update">
        <a-steps progress-dot :current="currentStep" :items="steps" />

        <div v-if="currentStep === 0" class="list">
          <template v-if="otaList && otaList.length">
            <div
              v-for="(ota, index) in otaList"
              :key="String(ota.fileId)"
              class="item"
              :class="{ selected: String((ota as any).fileId) === selectedFileId }"
              @click="selectOta(ota)"
              @dblclick="onNext"
              role="button"
              tabindex="0"
              @keydown.enter.prevent="selectOta(ota)"
              :title="ota.fileName"
            >
              <div class="item-index">{{ index + 1 }}</div>
              <div class="item-name">{{ ota.fileName }}</div>
              <div class="item-time">{{ ota.createTime }}</div>
            </div>
          </template>
          <template v-else>
            <div class="item"
              >暂无数据 <a-button type="link" @click="fetchOTAList">刷新</a-button></div
            >
          </template>
        </div>

        <div v-if="currentStep === 1" class="device-select">
          <a-space direction="vertical" style="width: 100%; margin-top: 12px">
            <div style="display: flex; gap: 8px; align-items: center">
              <a-input
                v-model:value="searchName"
                placeholder="设备名称"
                allowClear
                style="width: 150px"
                @keyup.enter="onSearchDevices"
              />
              <a-input
                v-model:value="searchId"
                placeholder="设备ID"
                allowClear
                style="width: 150px"
                @keyup.enter="onSearchDevices"
              />
              <a-button type="primary" @click="onSearchDevices">搜索</a-button>
              <a-button @click="reloadDevices">重置</a-button>
            </div>

            <div
              style="display: flex; justify-content: space-between; margin-top: 8px; color: #666"
            >
              <div v-if="selectedOta">
                <strong>已选择固件：</strong>
                <span
                  >{{ selectedOta.fileName }}
                  <span style="color: #aaa">({{ selectedOta.createTime }})</span></span
                >
              </div>
              已选中 {{ selectedClientIds.length }} 台设备
            </div>

            <a-table
              :columns="deviceColumns"
              :data-source="deviceList"
              :row-key="rowKey"
              :pagination="false"
              :loading="deviceLoading"
              :row-selection="rowSelection"
              :row-class-name="rowClassName"
              :scroll="{ y: 320 }"
            >
              <template #bodyCell="{ column, record }">
                <template v-if="column.key === 'name'">{{ record.name }}</template>
                <template v-else>{{ record[column.dataIndex as string] }}</template>
              </template>
            </a-table>
          </a-space>
        </div>

        <div v-if="currentStep === 2" class="finish">
          <a-result status="success" title="指令已发送" sub-title="设备升级时长约30秒，请耐心等待">
            <template #extra>
              <a-space direction="vertical">
                <a-button type="primary" @click="closeAfterFinish">我知道了</a-button>
              </a-space>
            </template>
          </a-result>
        </div>
      </div>

      <div class="footer" v-if="currentStep !== 2">
        <div class="footer-left">
          <a-button v-if="currentStep > 0" @click="onPrev">上一步</a-button>
        </div>
        <div class="footer-right">
          <a-button @click="cancel">取消</a-button>

          <a-button
            type="primary"
            :disabled="nextDisabled || upgrading"
            :loading="upgrading"
            @click="onPrimaryClick"
          >
            <span v-if="currentStep === 0">下一步</span>
            <span v-else-if="currentStep === 1">开始升级</span>
            <span v-else>完成</span>
          </a-button>
        </div>
      </div>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { ref, watch, computed, reactive } from 'vue'
import { message, Upload } from 'ant-design-vue'
import { InboxOutlined } from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
import axios, { type AxiosProgressEvent, type AxiosResponse } from 'axios'
import type { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface'
import * as deviceApi from '@/api/device'
import type { OtaItem } from '@/api/device/types'

defineOptions({ name: 'OTADevice' })

const userStore = useUserStore()

type Props = { open: boolean; title?: string }
const emit = defineEmits<{
  (e: 'update:open', value: boolean): void
  (e: 'success', payload: { ota: OtaItem; clientIds: string[] } | null): void
}>()

const props = withDefaults(defineProps<Props>(), { open: false, title: 'OTA升级' })

interface ApiResponse<T = unknown> {
  code?: number | string
  message?: string
  data?: T
}
type OtaFileId = string | number
type DeviceApiRowRaw = Record<string, unknown>
type DeviceRow = { clientId: string; id: string; name: string; status?: string }

type UploadFileItem = {
  uid?: string
  name?: string
  status?: string
  response?: unknown
  [k: string]: unknown
}
const fileList = ref<UploadFileItem[]>([])
const action = `${import.meta.env.VITE_API_BASE_URL}/system/OTAUpload`
const acceptType = '.bin,.hex,.fw'
const handleChange = (info: { file: UploadFileItem; fileList: UploadFileItem[] }) => {
  const status = info.file.status
  if (status !== 'uploading') console.log(info.file, info.fileList)
  if (status === 'done') console.log(`${info.file.name} file uploaded successfully.`)
  else if (status === 'error') console.log(`${info.file.name} file upload failed.`)
}
function handleDrop(e: DragEvent) {
  if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
    const file = e.dataTransfer.files[0]
    const isValid = beforeUpload(file)
    if (isValid !== true) fileList.value = []
  }
}
const headers = {
  'content-type': 'multipart/form-data',
  Authorization: userStore.userInfo.tokenValue,
}
interface CustomUploadFile extends File {
  uid: string
  readonly lastModifiedDate: Date
}
type UploadProgressHandler = (progressEvent: {
  percent: number
  loaded: number
  total: number
  event: ProgressEvent
}) => void
const repeateIds = ref<string[]>()
type UploadSuccessHandler = <T = unknown>(response: T, event?: ProgressEvent) => void

const customRequest = (options: UploadRequestOption) => {
  const { file, onProgress, onSuccess, onError } = options
  const customFile = file as CustomUploadFile
  const formData = new FormData()
  formData.append('file', customFile)
  const handleProgress: UploadProgressHandler = (progressEvent) => {
    const percent = Math.min(100, Math.max(0, progressEvent.percent))
    onProgress?.({ percent })
  }
  const handleSuccess: UploadSuccessHandler = (response) => onSuccess?.(response)
  repeateIds.value = []
  axios
    .post(action, formData, {
      headers: { 'Content-Type': 'multipart/form-data' },
      onUploadProgress: (progressEvent: AxiosProgressEvent) => {
        if (!progressEvent.total) return
        const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
        handleProgress({
          percent,
          loaded: progressEvent.loaded,
          total: progressEvent.total,
          event: progressEvent.event as ProgressEvent,
        })
      },
    })
    .then((response: AxiosResponse) => {
      if (['200', 200].includes(response.data?.code)) {
        handleSuccess(response.data)
        message.success('上传成功')
        void fetchOTAList()
      } else {
        const errorMsg = response.data?.message ?? '上传接口返回错误'
        message.error(errorMsg)
        onError?.(response.data)
      }
    })
    .catch((error: unknown) => {
      const err = error as { code?: string; message?: string }
      const errorMsg =
        err?.code === 'ERR_NETWORK' ? '网络异常，请重试' : (err?.message ?? '上传出错')
      message.error(errorMsg)
      onError?.(error as Error)
    })
  return {
    abort() {
      console.log('上传取消', customFile.name)
    },
  }
}
const allowedExt = ['.bin', '.hex', '.fw']
const beforeUpload = (file: File) => {
  const lowerName = file.name.toLowerCase()
  const hasAllowedExt = allowedExt.some((ext) => lowerName.endsWith(ext))
  if (!hasAllowedExt) {
    message.error(`只能上传 ${allowedExt.join(', ')} 格式的文件!`)
    return Upload.LIST_IGNORE
  }
  const isLt10M = file.size / 1024 / 1024 <= 10
  if (!isLt10M) {
    message.error('文件大小不能超过10MB!')
    return Upload.LIST_IGNORE
  }
  return true
}

const otaList = ref<OtaItem[]>([])
const selectedFileId = ref<string | null>(null)
const selectedOta = computed(() => {
  const found = otaList.value.find((i) => {
    const fid = (i as { fileId?: OtaFileId }).fileId
    return fid !== undefined && String(fid) === selectedFileId.value
  })
  return found ?? null
})

const currentStep = ref<number>(0)
const lastStep = 2
const steps = [{ title: '选择固件' }, { title: '选择设备' }, { title: '升级结束' }]

// 设备数据 & 搜索
const deviceList = ref<DeviceRow[]>([])
const deviceLoading = ref(false)
const searchName = ref<string>('')
const searchId = ref<string>('')

// 选中 clientId 集合
const selectedClientIds = ref<string[]>([])

// 升级状态
const upgrading = ref(false)

// 表格列
const deviceColumns = [
  { title: '设备名称', dataIndex: 'name', key: 'name' },
  { title: '设备ID', dataIndex: 'clientId', key: 'clientId', width: 220 },
  { title: '设备状态', dataIndex: 'status', key: 'status', width: 120 },
]

const rowKey = (r: DeviceRow) => r.clientId

type RowSelection = {
  type: 'checkbox'
  selectedRowKeys: string[]
  onChange: (selectedRowKeys: string[]) => void
  preserveSelectedRowKeys?: boolean
}
const rowSelection = reactive<RowSelection>({
  type: 'checkbox',
  selectedRowKeys: [],
  onChange: (selectedRowKeys: string[]) => {
    selectedClientIds.value = selectedRowKeys
  },
  preserveSelectedRowKeys: true,
})
watch(
  selectedClientIds,
  (keys) => {
    rowSelection.selectedRowKeys = Array.isArray(keys) ? [...keys] : []
  },
  { deep: true, immediate: true }
)

function rowClassName(record: DeviceRow) {
  return selectedClientIds.value.includes(record.clientId) ? 'custom-row-selected' : ''
}

const nextDisabled = computed(() => {
  if (currentStep.value === 0) return !selectedFileId.value
  if (currentStep.value === 1) return selectedClientIds.value.length === 0
  return false
})

const fetchOTAList = async (): Promise<void> => {
  try {
    const resp = (await deviceApi.getOtaList()) as ApiResponse<OtaItem[]>
    if (Array.isArray(resp?.data)) {
      otaList.value = resp.data
      if (otaList.value.length && !selectedFileId.value) {
        selectedFileId.value = String((otaList.value[0] as { fileId?: OtaFileId }).fileId ?? '')
      }
    } else {
      otaList.value = []
    }
  } catch (err) {
    console.error('获取 OTA 列表失败', err)
    otaList.value = []
  }
}

const fetchDeviceList = async (): Promise<void> => {
  deviceLoading.value = true
  try {
    const params = {
      pageNo: 1,
      pageSize: 1000,
      clientId: searchId.value || undefined,
      devName: searchName.value || undefined,
      createTimeStart: undefined,
      createTimeEnd: undefined,
      online: null,
    }
    const res = (await deviceApi.getDeviceList(params)) as ApiResponse<unknown>
    const data = res?.data as Record<string, unknown> | undefined
    const rows = (data?.rows ?? data?.list) as unknown
    if (Array.isArray(rows)) {
      deviceList.value = rows.map((r) => {
        const row = r as DeviceApiRowRaw
        const clientId = String(row.clientId ?? '')
        const name = String(row.devName ?? '')
        const onlineVal = row.online
        let status = '未知'
        if (onlineVal === 1 || onlineVal === '1') {
          status = '在线'
        } else if (onlineVal === 0 || onlineVal === '0') {
          status = '离线'
        } else if (onlineVal === 9 || onlineVal === '9') {
          status = '未激活'
        }

        return { clientId, id: clientId, name, status }
      })
      selectedClientIds.value = selectedClientIds.value.filter((id) =>
        deviceList.value.some((d) => d.clientId === id)
      )
    } else {
      deviceList.value = []
      selectedClientIds.value = []
    }
  } catch (err) {
    console.error('获取设备列表失败', err)
    deviceList.value = []
    selectedClientIds.value = []
  } finally {
    deviceLoading.value = false
  }
}

const onSearchDevices = async (): Promise<void> => {
  await fetchDeviceList()
}
const reloadDevices = (): void => {
  searchName.value = ''
  searchId.value = ''
  void fetchDeviceList()
}

watch(
  () => props.open,
  (val) => {
    if (val) {
      void fetchOTAList()
      currentStep.value = 0
      selectedFileId.value = null
      selectedClientIds.value = []
      deviceList.value = []
      searchName.value = ''
      searchId.value = ''
    }
  }
)

// 选择固件
function selectOta(ota: OtaItem): void {
  const id = String((ota as { fileId?: OtaFileId }).fileId ?? '')
  selectedFileId.value = selectedFileId.value === id ? null : id
}

function onPrev(): void {
  if (currentStep.value > 0) currentStep.value -= 1
}
async function onNext(): Promise<void> {
  if (currentStep.value === 0) {
    if (!selectedFileId.value) {
      message.warn('请先选择固件')
      return
    }
    currentStep.value = 1
    await fetchDeviceList()
    return
  }
  if (currentStep.value === 1) {
    message.info('请点击底部主按钮“开始升级”以发起升级')
    return
  }
}

// 主按钮点击处理
async function onPrimaryClick(): Promise<void> {
  if (currentStep.value === 0) {
    await onNext()
    return
  }
  if (currentStep.value === 1) {
    await startUpgrade()
    return
  }
  if (currentStep.value === lastStep) {
    closeAfterFinish()
  }
}

const startUpgrade = async (): Promise<void> => {
  if (!selectedOta.value) {
    message.warn('请选择固件')
    return
  }
  if (selectedClientIds.value.length === 0) {
    message.warn('请至少选择一台设备')
    return
  }

  const ossUrl = (selectedOta.value as unknown as { ossUrl?: string }).ossUrl ?? ''
  if (!ossUrl) {
    message.error('所选固件未包含下载地址 ossUrl，无法发起升级')
    return
  }

  upgrading.value = true

  try {
    const payload = { clientIds: selectedClientIds.value, ossUrl }
    await deviceApi.updateOta(payload)

    // 延迟 3 秒再进入结果页
    await new Promise((resolve) => setTimeout(resolve, 3000))
    currentStep.value = 2
    emit('success', { ota: selectedOta.value as OtaItem, clientIds: selectedClientIds.value })
  } catch (err) {
    console.error('发起升级失败', err)
    message.error('发起升级失败，请重试')
  } finally {
    upgrading.value = false
  }
}

function closeAfterFinish(): void {
  emit('update:open', false)
}
function cancel(): void {
  fileList.value = []
  repeateIds.value = []
  selectedFileId.value = null
  selectedClientIds.value = []
  currentStep.value = 0
  emit('update:open', false)
}
</script>

<style scoped lang="less">
:deep(.ant-modal) {
  .header {
    display: flex;
    align-items: center;
  }

  .update {
    margin-top: 16px;
  }

  .list {
    height: 400px;
    overflow-y: auto;
  }

  .list,
  .device-select,
  .finish {
    background-color: #f7f7f7;
    padding: 12px;
    border-radius: 8px;
    margin-top: 16px;
    min-height: 400px;
  }

  .item {
    display: flex;
    gap: 12px;
    align-items: center;
    padding: 8px;
    border: 1px solid transparent;
    border-radius: 6px;
    cursor: pointer;
    color: #333;
    opacity: 0.8;
  }

  .item:hover {
    opacity: 1;
  }

  .item.selected {
    background: #e6f7ff;
    border-color: #91d5ff;
    box-shadow: 0 0 0 2px rgba(145, 213, 255, 0.12);
  }

  .item-index {
    width: 36px;
    text-align: center;
    color: #1677ff;
    font-weight: 600;
  }

  .item-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .item-time {
    width: 180px;
    text-align: right;
    color: #888;
    font-size: 12px;
  }

  .placeholder {
    padding: 12px;
    color: #666;
  }

  .footer {
    margin-top: 12px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
  }

  .footer-right {
    display: flex;
    gap: 8px;
  }

  .custom-row-selected {
    background: #e6f7ff !important;
    box-shadow: inset 0 0 0 1px rgba(24, 144, 255, 0.12);
  }

  .ant-result {
    padding: 16px;
  }
}
</style>
