<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
import type { UploadFile, UploadInstance, UploadRawFile } from 'element-plus';
import { userGenderOptions } from '@/constants/business';
import {
  deleteFile,
  downloadFile,
  fetchAssignUserRoles,
  fetchCreateUser,
  fetchDeleteUserSignature,
  fetchGetUser,
  fetchGetUserRoleIds,
  fetchGetUserSignature,
  fetchSaveUserSignature,
  fetchUpdateUser,
  uploadFile
} from '@/service/api';
import { useAuth } from '@/hooks/business/auth';
import { useForm, useFormRules } from '@/hooks/common/form';
import { translateOptions } from '@/utils/common';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import BusinessFormSection from '@/components/custom/business-form-section.vue';
import { $t } from '@/locales';

defineOptions({ name: 'UserOperateDialog' });

interface Props {
  operateType: UI.TableOperateType;
  userId?: number | null;
  currentDeptId?: number | null;
  deptTree: Api.SystemManage.Dept[];
  postOptions: Api.SystemManage.PostSimple[];
  roleOptions: Api.SystemManage.RoleSimple[];
  companyOptions: Api.Dict.DictData[];
}

const props = defineProps<Props>();

interface Emits {
  (e: 'submitted', userId?: number): void;
}

const emit = defineEmits<Emits>();

const visible = defineModel<boolean>('visible', {
  default: false
});

const { formRef, validate } = useForm();
const { createRequiredRule, patternRules } = useFormRules();
const { hasAuth } = useAuth();

const SIGNATURE_QUERY_PERMISSION = 'system:user-signature:query';
const SIGNATURE_UPDATE_PERMISSION = 'system:user-signature:update';
const SIGNATURE_MAX_SIZE = 5 * 1024 * 1024;

const loading = ref(false);
const submitting = ref(false);
const signatureLoading = ref(false);
const signatureUploading = ref(false);
const signatureUploadRef = ref<UploadInstance>();
const originalSignature = ref<Api.SystemManage.UserSignature | null>(null);
const currentSignatureFileId = ref<string | null>(null);
const currentSignatureFileName = ref<string | null>(null);
const currentSignatureSource = ref<'none' | 'original' | 'uploaded'>('none');
const currentSignaturePreviewUrl = ref('');
const currentSignatureObjectUrl = ref<string | null>(null);
const signatureDirty = ref(false);
const keepUploadedSignatureOnClose = ref(false);

const title = computed(() => {
  const titles: Record<UI.TableOperateType, string> = {
    add: $t('page.system.user.addUser'),
    edit: $t('page.system.user.editUser')
  };

  return titles[props.operateType];
});

const isEdit = computed(() => props.operateType === 'edit');
const canQuerySignature = computed(() => hasAuth(SIGNATURE_QUERY_PERMISSION));
const canUpdateSignature = computed(() => hasAuth(SIGNATURE_UPDATE_PERMISSION));
const showSignatureSection = computed(() => canQuerySignature.value);
const hasSignaturePreview = computed(() => Boolean(currentSignaturePreviewUrl.value));
const signatureReadonly = computed(() => !canUpdateSignature.value);

type Model = Api.SystemManage.SaveUserParams & {
  roleIds: string[];
};

const model = ref<Model>(createDefaultModel());

const genderOptions = computed(() =>
  translateOptions(userGenderOptions).map(item => ({
    ...item,
    value: Number(item.value) as Api.SystemManage.UserGender
  }))
);

const deptTreeProps = {
  value: 'id',
  label: 'name',
  children: 'children'
} as const;

function createDefaultModel(): Model {
  return {
    username: '',
    nickname: '',
    remark: '',
    deptId: props.currentDeptId ?? 0,
    positionId: null,
    company: null,
    email: '',
    mobile: '',
    sex: 1,
    avatar: '',
    sort: 0,
    password: '',
    roleIds: []
  };
}

function getNullableText(value?: string | null) {
  return value?.trim() || null;
}

function revokeSignaturePreviewUrl() {
  if (currentSignatureObjectUrl.value) {
    URL.revokeObjectURL(currentSignatureObjectUrl.value);
    currentSignatureObjectUrl.value = null;
  }

  currentSignaturePreviewUrl.value = '';
}

function setSignaturePreviewFromBlob(blob: Blob) {
  revokeSignaturePreviewUrl();
  currentSignatureObjectUrl.value = URL.createObjectURL(blob);
  currentSignaturePreviewUrl.value = currentSignatureObjectUrl.value;
}

function syncSignatureDirtyState() {
  signatureDirty.value = (originalSignature.value?.fileId ?? null) !== currentSignatureFileId.value;
}

async function cleanupUploadedSignatureFile(fileId?: string | null) {
  if (!fileId) return;

  await deleteFile(fileId).catch(() => undefined);
}

async function cleanupSignatureState(options?: { preserveUploaded?: boolean }) {
  const preserveUploaded = Boolean(options?.preserveUploaded);
  const uploadedFileId =
    currentSignatureSource.value === 'uploaded' &&
    currentSignatureFileId.value &&
    currentSignatureFileId.value !== originalSignature.value?.fileId
      ? currentSignatureFileId.value
      : null;

  if (!preserveUploaded && uploadedFileId) {
    await cleanupUploadedSignatureFile(uploadedFileId);
  }

  signatureUploadRef.value?.clearFiles();
  revokeSignaturePreviewUrl();
  originalSignature.value = null;
  currentSignatureFileId.value = null;
  currentSignatureFileName.value = null;
  currentSignatureSource.value = 'none';
  signatureDirty.value = false;
  signatureLoading.value = false;
  signatureUploading.value = false;
}

async function loadSignaturePreview(fileId: string) {
  const { error, data } = await downloadFile(fileId);

  if (error || !data) {
    revokeSignaturePreviewUrl();
    return;
  }

  setSignaturePreviewFromBlob(data);
}

async function initSignatureState() {
  await cleanupSignatureState({ preserveUploaded: true });

  if (!showSignatureSection.value || !isEdit.value || !props.userId) {
    return;
  }

  signatureLoading.value = true;
  const { error, data } = await fetchGetUserSignature(String(props.userId));
  signatureLoading.value = false;

  if (error || !data?.fileId) {
    return;
  }

  originalSignature.value = data;
  currentSignatureFileId.value = data.fileId;
  currentSignatureFileName.value = data.fileName ?? null;
  currentSignatureSource.value = 'original';
  syncSignatureDirtyState();
  await loadSignaturePreview(data.fileId);
}

function validateSignatureFile(file: UploadRawFile) {
  if (!file.type.startsWith('image/')) {
    window.$message?.warning('电子签名仅支持图片文件');
    return false;
  }

  if (file.size > SIGNATURE_MAX_SIZE) {
    window.$message?.warning('电子签名图片不能超过 5MB');
    return false;
  }

  return true;
}

async function handleSignatureFileChange(file: UploadFile) {
  const rawFile = file.raw;
  signatureUploadRef.value?.clearFiles();

  if (!rawFile || !canUpdateSignature.value || !validateSignatureFile(rawFile)) {
    return;
  }

  signatureUploading.value = true;
  const { error, data } = await uploadFile(rawFile, 'system/user-signatures');
  signatureUploading.value = false;

  if (error || !data) {
    return;
  }

  const previousUploadedFileId =
    currentSignatureSource.value === 'uploaded' &&
    currentSignatureFileId.value &&
    currentSignatureFileId.value !== originalSignature.value?.fileId
      ? currentSignatureFileId.value
      : null;

  if (previousUploadedFileId && previousUploadedFileId !== data.id) {
    await cleanupUploadedSignatureFile(previousUploadedFileId);
  }

  currentSignatureFileId.value = data.id;
  currentSignatureFileName.value = data.path.split('/').pop() || rawFile.name;
  currentSignatureSource.value = 'uploaded';
  setSignaturePreviewFromBlob(rawFile);
  syncSignatureDirtyState();
  window.$message?.success('电子签名上传成功');
}

async function clearSignature() {
  if (!canUpdateSignature.value) return;

  const uploadedFileId =
    currentSignatureSource.value === 'uploaded' &&
    currentSignatureFileId.value &&
    currentSignatureFileId.value !== originalSignature.value?.fileId
      ? currentSignatureFileId.value
      : null;

  if (uploadedFileId) {
    await cleanupUploadedSignatureFile(uploadedFileId);
  }

  currentSignatureFileId.value = null;
  currentSignatureFileName.value = null;
  currentSignatureSource.value = 'none';
  signatureUploadRef.value?.clearFiles();
  revokeSignaturePreviewUrl();
  syncSignatureDirtyState();
}

async function syncUserSignature(userId: number) {
  if (!showSignatureSection.value || !canUpdateSignature.value || !signatureDirty.value) {
    return true;
  }

  const originalFileId = originalSignature.value?.fileId ?? null;
  const currentFileId = currentSignatureFileId.value;

  if (!currentFileId) {
    if (!originalFileId) {
      return true;
    }

    const result = await fetchDeleteUserSignature(String(userId));
    if (result.error) {
      return false;
    }

    await cleanupUploadedSignatureFile(originalFileId);
    return true;
  }

  const result = await fetchSaveUserSignature({
    userId: String(userId),
    fileId: currentFileId,
    fileName: currentSignatureFileName.value ?? null,
    status: 0,
    remark: null
  });

  if (result.error) {
    return false;
  }

  if (originalFileId && originalFileId !== currentFileId) {
    await cleanupUploadedSignatureFile(originalFileId);
  }

  return true;
}

const rules = computed(() => {
  const passwordRules = isEdit.value
    ? []
    : [createRequiredRule($t('page.system.user.form.password')), patternRules.pwd];

  return {
    username: [createRequiredRule($t('page.system.user.form.userName')), patternRules.userName],
    nickname: [createRequiredRule($t('page.system.user.form.nickName'))],
    deptId: [createRequiredRule($t('page.system.user.form.deptName'))],
    positionId: [createRequiredRule($t('page.system.user.form.positionName'))],
    mobile: getNullableText(model.value.mobile) ? [patternRules.phone] : [],
    email: getNullableText(model.value.email) ? [patternRules.email] : [],
    password: passwordRules
  } satisfies Record<string, App.Global.FormRule[]>;
});

async function handleInitModel() {
  model.value = createDefaultModel();
  await initSignatureState();

  if (!isEdit.value || !props.userId) {
    return;
  }

  loading.value = true;

  const [userResult, roleResult] = await Promise.all([fetchGetUser(props.userId), fetchGetUserRoleIds(props.userId)]);

  loading.value = false;

  if (userResult.error) {
    return;
  }

  const user = userResult.data;

  model.value = {
    username: user.username,
    nickname: user.nickname ?? '',
    remark: user.remark ?? '',
    deptId: user.deptId,
    positionId: user.positionId ?? null,
    company: user.company ?? null,
    email: user.email ?? '',
    mobile: user.mobile ?? '',
    sex: user.sex ?? 0,
    avatar: user.avatar ?? '',
    sort: user.sort ?? 0,
    password: '',
    roleIds: roleResult.error ? [] : roleResult.data
  };
}

function closeDialog() {
  visible.value = false;
}

async function handleSubmit() {
  await validate();

  const payload: Api.SystemManage.SaveUserParams = {
    username: model.value.username.trim(),
    nickname: getNullableText(model.value.nickname),
    remark: getNullableText(model.value.remark),
    deptId: model.value.deptId,
    positionId: model.value.positionId,
    company: model.value.company,
    email: getNullableText(model.value.email),
    mobile: getNullableText(model.value.mobile),
    sex: model.value.sex,
    avatar: getNullableText(model.value.avatar),
    sort: model.value.sort
  };

  if (!isEdit.value) {
    payload.password = String(model.value.password ?? '').trim();
  }

  submitting.value = true;

  let userId = props.userId ?? undefined;

  if (isEdit.value && props.userId) {
    const result = await fetchUpdateUser({ id: props.userId, ...payload });

    if (result.error) {
      submitting.value = false;
      return;
    }
  } else {
    const result = await fetchCreateUser(payload);

    if (result.error) {
      submitting.value = false;
      return;
    }

    userId = result.data;
  }

  if (userId !== undefined) {
    const roleResult = await fetchAssignUserRoles({
      userId,
      roleIds: model.value.roleIds
    });

    if (roleResult.error) {
      submitting.value = false;
      return;
    }

    const signatureSynced = await syncUserSignature(userId);

    if (!signatureSynced) {
      submitting.value = false;
      return;
    }
  }

  submitting.value = false;
  keepUploadedSignatureOnClose.value = true;

  window.$message?.success(isEdit.value ? $t('common.updateSuccess') : $t('common.addSuccess'));
  closeDialog();
  emit('submitted', userId);
}

watch(visible, async value => {
  if (!value) {
    const preserveUploaded = keepUploadedSignatureOnClose.value;
    keepUploadedSignatureOnClose.value = false;
    await cleanupSignatureState({ preserveUploaded });
    return;
  }

  await handleInitModel();
  await nextTick();
  formRef.value?.clearValidate();
});

onBeforeUnmount(() => {
  revokeSignaturePreviewUrl();
});
</script>

<template>
  <BusinessFormDialog
    v-model="visible"
    :title="title"
    preset="md"
    :loading="loading"
    :confirm-loading="submitting"
    max-body-height="70vh"
    @confirm="handleSubmit"
  >
    <ElForm ref="formRef" :model="model" :rules="rules" label-position="top" autocomplete="off">
      <input class="business-form-autofill-guard" type="text" name="fake-username" autocomplete="username" />
      <input class="business-form-autofill-guard" type="password" name="fake-password" autocomplete="new-password" />

      <BusinessFormSection :title="$t('page.system.user.sections.basicInfo')">
        <ElRow :gutter="16">
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.userName')" prop="username">
              <ElInput
                v-model="model.username"
                name="system-user-username"
                autocomplete="off"
                :placeholder="$t('page.system.user.form.userName')"
              />
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.nickName')" prop="nickname">
              <ElInput
                v-model="model.nickname"
                name="system-user-nickname"
                autocomplete="off"
                :placeholder="$t('page.system.user.form.nickName')"
              />
            </ElFormItem>
          </ElCol>
          <ElCol v-if="!isEdit" :span="12">
            <ElFormItem :label="$t('page.system.user.password')" prop="password">
              <ElInput
                v-model="model.password"
                name="system-user-password"
                show-password
                autocomplete="new-password"
                :placeholder="$t('page.system.user.form.password')"
              />
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.userGender')" prop="sex">
              <ElSelect v-model="model.sex" :placeholder="$t('page.system.user.form.userGender')">
                <ElOption v-for="{ label, value } in genderOptions" :key="value" :label="label" :value="value" />
              </ElSelect>
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem label="排序" prop="sort">
              <ElInputNumber v-model="model.sort" class="w-full" :min="0" placeholder="请输入排序" />
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.userPhone')" prop="mobile">
              <ElInput
                v-model="model.mobile"
                name="system-user-mobile"
                autocomplete="off"
                :placeholder="$t('page.system.user.form.userPhone')"
              />
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.userEmail')" prop="email">
              <ElInput
                v-model="model.email"
                name="system-user-email"
                autocomplete="off"
                :placeholder="$t('page.system.user.form.userEmail')"
              />
            </ElFormItem>
          </ElCol>
          <ElCol :span="24">
            <ElFormItem :label="$t('page.system.user.remark')" prop="remark">
              <ElInput
                v-model="model.remark"
                type="textarea"
                :rows="3"
                :placeholder="$t('page.system.user.form.remark')"
              />
            </ElFormItem>
          </ElCol>
        </ElRow>
      </BusinessFormSection>

      <BusinessFormSection :title="$t('page.system.user.sections.organizationInfo')">
        <ElRow :gutter="16">
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.deptName')" prop="deptId">
              <ElTreeSelect
                v-model="model.deptId"
                check-strictly
                clearable
                default-expand-all
                :data="deptTree"
                :props="deptTreeProps"
                :render-after-expand="false"
                :placeholder="$t('page.system.user.form.deptName')"
              />
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.positionName')" prop="positionId">
              <ElSelect v-model="model.positionId" clearable :placeholder="$t('page.system.user.form.positionName')">
                <ElOption v-for="item in postOptions" :key="item.id" :label="item.name" :value="item.id" />
              </ElSelect>
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem label="所属公司" prop="company">
              <ElSelect v-model="model.company" clearable filterable placeholder="请选择所属公司">
                <ElOption v-for="item in companyOptions" :key="item.value" :label="item.label" :value="item.value" />
              </ElSelect>
            </ElFormItem>
          </ElCol>
          <ElCol :span="12">
            <ElFormItem :label="$t('page.system.user.userRole')" prop="roleIds">
              <ElSelect
                v-model="model.roleIds"
                multiple
                collapse-tags
                collapse-tags-tooltip
                :placeholder="$t('page.system.user.form.userRole')"
              >
                <ElOption v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
              </ElSelect>
            </ElFormItem>
          </ElCol>
        </ElRow>
      </BusinessFormSection>

      <BusinessFormSection v-if="showSignatureSection" title="电子签名">
        <div v-loading="signatureLoading" class="signature-section">
          <div class="signature-preview-card">
            <div v-if="hasSignaturePreview" class="signature-preview-box">
              <img :src="currentSignaturePreviewUrl" alt="电子签名预览" class="signature-preview-image" />
            </div>
            <div v-else class="signature-preview-empty">
              {{ signatureReadonly ? '未维护电子签名' : '请上传电子签名图片' }}
            </div>
            <div class="signature-preview-meta">
              <div class="signature-preview-name">{{ currentSignatureFileName || '未选择文件' }}</div>
              <div class="signature-preview-tip">仅支持图片，大小不超过 5MB</div>
            </div>
          </div>

          <div v-if="canUpdateSignature" class="signature-actions">
            <ElUpload
              ref="signatureUploadRef"
              :show-file-list="false"
              :auto-upload="false"
              accept="image/*"
              :on-change="handleSignatureFileChange"
            >
              <ElButton plain type="primary" :loading="signatureUploading">
                {{ hasSignaturePreview ? '重新上传' : '上传签名' }}
              </ElButton>
            </ElUpload>
            <ElButton plain :disabled="!currentSignatureFileId || signatureUploading" @click="clearSignature">
              清空
            </ElButton>
          </div>
        </div>
      </BusinessFormSection>
    </ElForm>
  </BusinessFormDialog>
</template>

<style scoped>
.business-form-autofill-guard {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

.signature-section {
  display: grid;
  gap: 12px;
}

.signature-preview-card {
  display: grid;
  gap: 10px;
}

.signature-preview-box,
.signature-preview-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 160px;
  padding: 12px;
  border: 1px dashed var(--el-border-color);
  border-radius: 8px;
  background: var(--el-fill-color-lighter);
}

.signature-preview-image {
  max-width: 100%;
  max-height: 136px;
  object-fit: contain;
}

.signature-preview-empty {
  color: var(--el-text-color-secondary);
  font-size: 13px;
}

.signature-preview-meta {
  display: grid;
  gap: 4px;
}

.signature-preview-name {
  color: var(--el-text-color-primary);
  font-size: 13px;
}

.signature-preview-tip {
  color: var(--el-text-color-secondary);
  font-size: 12px;
}

.signature-actions {
  display: flex;
  gap: 8px;
}
</style>
