import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Link, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import {
  Alert,
  Button,
  Checkbox,
  Descriptions,
  Drawer,
  Form,
  Input,
  Modal,
  Select,
  Space,
  Table,
  Tag,
  Typography,
  message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { USER_SOURCE_TYPE_LABELS, type UserSourceType } from '@dukang/shared-types';
import { request, type AdminUserRow, type HqProfile, type Paginated } from '../lib/api';
import { useAdminListColumns } from '../lib/useAdminListColumns';
import { AdminListHeader } from '../components/AdminListHeader';
import { AdminPrimaryLink } from '../components/AdminPrimaryLink';
import { ADMIN_OPTIONS_PAGE_SIZE, ORDER_STATUS_LABELS, fmtTime, maskPhone } from '../lib/constants';
import { clientAppLabel, eventNameLabel, userEventCategoryLabel } from '../lib/display-labels';

type UserOrderRow = {
  id: string;
  orderNo: string;
  status: string;
  payAmount: number;
  payStatus?: string;
  createdAt: string;
};

function HqRemarkCell({
  id,
  hqRemark,
  onSaved,
}: {
  id: string;
  hqRemark: string | null;
  onSaved: (id: string, hqRemark: string | null) => void;
}) {
  const [editing, setEditing] = useState(false);
  const [value, setValue] = useState(hqRemark ?? '');
  const savingRef = useRef(false);

  useEffect(() => {
    if (!editing) setValue(hqRemark ?? '');
  }, [hqRemark, editing]);

  async function commit() {
    if (savingRef.current) return;
    const next = value.trim() || null;
    const prev = hqRemark?.trim() || null;
    setEditing(false);
    if (next === prev) return;
    savingRef.current = true;
    try {
      const res = await request<{ id: string; hqRemark: string | null }>(`/admin/users/${id}`, {
        method: 'PUT',
        body: JSON.stringify({ hqRemark: next ?? '' }),
      });
      onSaved(id, res.hqRemark);
      message.success('备注已保存');
    } catch (e) {
      message.error(e instanceof Error ? e.message : '保存失败');
      setValue(hqRemark ?? '');
    } finally {
      savingRef.current = false;
    }
  }

  if (editing) {
    return (
      <Input
        size="small"
        autoFocus
        maxLength={128}
        value={value}
        onChange={(e) => setValue(e.target.value)}
        onBlur={() => void commit()}
        onPressEnter={() => void commit()}
        onKeyDown={(e) => {
          if (e.key === 'Escape') void commit();
        }}
        onClick={(e) => e.stopPropagation()}
      />
    );
  }

  return (
    <span
      title="双击修改备注，离开后保存"
      onDoubleClick={(e) => {
        e.stopPropagation();
        setEditing(true);
      }}
      style={{ cursor: 'text', display: 'inline-block', minWidth: 48 }}
    >
      {hqRemark || '—'}
    </span>
  );
}

/** 好客权益金额展示：0 与空值统一显示占位，避免整列都是 ¥0.00 干扰 */
function fmtBenefit(v: number | null | undefined) {
  const n = Number(v ?? 0);
  if (!Number.isFinite(n) || n <= 0) return '—';
  return `¥${n.toFixed(2)}`;
}

type UserBehaviorLog = {
  id: string;
  eventName: string;
  clientApp?: string | null;
  createdAt: string;
  extraJson?: Record<string, unknown> | null;
};

type AssocPartnerBrief = {
  id: string;
  name: string;
  companyName?: string | null;
  phone?: string | null;
};

type PartnerOption = { id: string; companyName?: string | null; name?: string; phone?: string };

const FILTERS_COLLAPSED_KEY = 'admin-users-filters-collapsed';
const ASSOC_UNBOUND = 'none';

function formatAssocPartner(p?: AssocPartnerBrief | PartnerOption | null) {
  if (!p) return '—';
  const title = p.companyName || p.name || p.id;
  return [title, p.phone].filter(Boolean).join(' / ') || '—';
}

type UserDetail = AdminUserRow & {
  wxUnionId?: string | null;
  cityPref?: Record<string, unknown> | null;
  mergedInto?: { id: string; userNo: string; phone: string | null; nickname: string | null } | null;
  sourcePromo?: { id: string; code: string; name: string } | null;
  orders?: UserOrderRow[];
  mergedFromCount?: number;
  addressCount?: number;
  assocPartner?: AssocPartnerBrief | null;
  assocBoundAt?: string | null;
};

type BatchDeletePreviewItem = {
  id: string;
  userNo: string;
  nickname: string | null;
  phone: string | null;
  hasRisk: boolean;
  unfinishedOrders: UserOrderRow[];
  redeemRecords: Array<{
    id: string;
    redeemNo: string;
    amount: number;
    createdAt: string;
    payoutStatus: string | null;
  }>;
};

type BatchDeletePreview = {
  items: BatchDeletePreviewItem[];
  hasRisk: boolean;
  total: number;
};

export default function UsersPage() {
  const navigate = useNavigate();
  const location = useLocation();
  const [searchParams, setSearchParams] = useSearchParams();
  const [form] = Form.useForm();
  const [profile, setProfile] = useState<HqProfile | null>(null);
  const [data, setData] = useState<Paginated<AdminUserRow> | null>(null);
  const [loading, setLoading] = useState(false);
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);
  const [detail, setDetail] = useState<UserDetail | null>(null);
  const [behaviorLogs, setBehaviorLogs] = useState<UserBehaviorLog[]>([]);
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [deleteOpen, setDeleteOpen] = useState(false);
  const [deleteConfirm, setDeleteConfirm] = useState('');
  const [deleting, setDeleting] = useState(false);
  const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
  const [batchDeleteOpen, setBatchDeleteOpen] = useState(false);
  const [batchDeleteStep, setBatchDeleteStep] = useState<1 | 2>(1);
  const [batchPreview, setBatchPreview] = useState<BatchDeletePreview | null>(null);
  const [batchPreviewLoading, setBatchPreviewLoading] = useState(false);
  const [batchDeleting, setBatchDeleting] = useState(false);
  const [batchRiskAck, setBatchRiskAck] = useState(false);
  const canDeleteUsers = (profile?.permissionKeys ?? []).includes('users_delete');
  const canEditAssoc = (profile?.permissionKeys ?? []).includes('users_partner_assoc');
  const [partners, setPartners] = useState<PartnerOption[]>([]);
  const [assocPartnerId, setAssocPartnerId] = useState<string | undefined>();
  const [savingAssoc, setSavingAssoc] = useState(false);
  const [filtersCollapsed, setFiltersCollapsed] = useState(() => {
    try {
      return localStorage.getItem(FILTERS_COLLAPSED_KEY) === '1';
    } catch {
      return false;
    }
  });

  const partnerOptions = useMemo(() => {
    const map = new Map(partners.map((p) => [p.id, p]));
    if (detail?.assocPartner && !map.has(detail.assocPartner.id)) {
      map.set(detail.assocPartner.id, detail.assocPartner);
    }
    return [...map.values()];
  }, [partners, detail?.assocPartner]);

  useEffect(() => {
    request<HqProfile>('/admin/auth/me').then(setProfile).catch(() => {});
  }, []);

  useEffect(() => {
    void request<Paginated<PartnerOption>>(`/admin/partners?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
      .then((res) => setPartners(res.items ?? []))
      .catch(() => setPartners([]));
  }, []);

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const values = form.getFieldsValue();
      const qs = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
      const userId = String(values.userId || '').trim();
      if (userId) qs.set('userId', userId);
      if (values.phone) qs.set('phone', values.phone);
      if (values.userNo) qs.set('userNo', values.userNo);
      if (values.phoneVerified !== undefined && values.phoneVerified !== '') {
        qs.set('phoneVerified', values.phoneVerified);
      }
      if (values.status !== undefined && values.status !== '') {
        qs.set('status', String(values.status));
      }
      const keyword = String(values.keyword || '').trim();
      if (keyword) qs.set('keyword', keyword);
      const assocPartnerAccountId = String(
        values.assocPartnerAccountId || searchParams.get('assocPartnerAccountId') || '',
      ).trim();
      if (assocPartnerAccountId) qs.set('assocPartnerAccountId', assocPartnerAccountId);
      const res = await request<Paginated<AdminUserRow>>(`/admin/users?${qs}`);
      setData(res);
    } finally {
      setLoading(false);
    }
  }, [form, page, pageSize, searchParams]);

  useEffect(() => {
    const userId = searchParams.get('userId');
    const assocPartnerAccountId = searchParams.get('assocPartnerAccountId');
    form.setFieldsValue({
      ...(userId ? { userId } : {}),
      ...(assocPartnerAccountId ? { assocPartnerAccountId } : {}),
    });
  }, [searchParams, form]);

  useEffect(() => {
    void load();
  }, [load, searchParams]);

  useEffect(() => {
    const openUserId = (location.state as { openUserId?: string } | null)?.openUserId;
    if (!openUserId) return;
    form.setFieldsValue({ userId: openUserId });
    setPage(1);
    void openDetail(openUserId);
    navigate(`${location.pathname}?userId=${encodeURIComponent(openUserId)}`, {
      replace: true,
      state: null,
    });
  }, [location.state, location.pathname, navigate, form]);

  async function openDetail(id: string) {
    const [res, logs] = await Promise.all([
      request<UserDetail>(`/admin/users/${id}`),
      request<{ items: UserBehaviorLog[] }>(`/admin/logs/users?userId=${id}&pageSize=50`).catch(
        () => ({ items: [] as UserBehaviorLog[] }),
      ),
    ]);
    setDetail(res);
    setAssocPartnerId(res.assocPartner?.id);
    setBehaviorLogs(logs.items ?? []);
    setDrawerOpen(true);
  }

  async function saveAssoc() {
    if (!detail) return;
    setSavingAssoc(true);
    try {
      await request(`/admin/users/${detail.id}/assoc`, {
        method: 'PUT',
        body: JSON.stringify({ partnerAccountId: assocPartnerId || null }),
      });
      message.success(assocPartnerId ? '已更新关联合伙人' : '已解除关联');
      await openDetail(detail.id);
      void load();
    } catch (e) {
      message.error(e instanceof Error ? e.message : '保存失败');
    } finally {
      setSavingAssoc(false);
    }
  }

  function openDeleteModal() {
    setDeleteConfirm('');
    setDeleteOpen(true);
  }

  async function confirmDelete() {
    if (!detail) return;
    if (deleteConfirm !== detail.userNo) {
      message.error('请输入正确的用户编号以确认删除');
      return;
    }
    setDeleting(true);
    try {
      await request(`/admin/users/${detail.id}`, { method: 'DELETE' });
      message.success('用户已删除（行为日志已保留）');
      setDeleteOpen(false);
      setDrawerOpen(false);
      setDetail(null);
      setSelectedRowKeys((keys) => keys.filter((k) => k !== detail.id));
      void load();
    } catch (e) {
      message.error(e instanceof Error ? e.message : '删除失败');
    } finally {
      setDeleting(false);
    }
  }

  async function openBatchDeleteModal() {
    if (!selectedRowKeys.length) return;
    setBatchDeleteStep(1);
    setBatchRiskAck(false);
    setBatchPreview(null);
    setBatchDeleteOpen(true);
    setBatchPreviewLoading(true);
    try {
      const res = await request<BatchDeletePreview>('/admin/users/batch-delete/preview', {
        method: 'POST',
        body: JSON.stringify({ ids: selectedRowKeys }),
      });
      setBatchPreview(res);
    } catch (e) {
      message.error(e instanceof Error ? e.message : '预检失败');
      setBatchDeleteOpen(false);
    } finally {
      setBatchPreviewLoading(false);
    }
  }

  async function confirmBatchDelete(confirmRisk: boolean) {
    if (!selectedRowKeys.length) return;
    setBatchDeleting(true);
    try {
      const res = await request<{ deleted: number; message: string }>('/admin/users/batch-delete', {
        method: 'POST',
        body: JSON.stringify({ ids: selectedRowKeys, confirmRisk }),
      });
      message.success(res.message || `已删除 ${res.deleted} 名用户`);
      setBatchDeleteOpen(false);
      setBatchPreview(null);
      if (detail && selectedRowKeys.includes(detail.id)) {
        setDrawerOpen(false);
        setDetail(null);
      }
      setSelectedRowKeys([]);
      void load();
    } catch (e) {
      message.error(e instanceof Error ? e.message : '批量删除失败');
    } finally {
      setBatchDeleting(false);
    }
  }

  function handleBatchDeleteOk() {
    if (!batchPreview) return;
    if (batchPreview.hasRisk && batchDeleteStep === 1) {
      setBatchDeleteStep(2);
      return;
    }
    void confirmBatchDelete(batchPreview.hasRisk);
  }

  const orderColumns: ColumnsType<UserOrderRow> = [
    { title: '订单号', dataIndex: 'orderNo', width: 170 },
    {
      title: '状态',
      dataIndex: 'status',
      width: 100,
      render: (s) => <Tag>{ORDER_STATUS_LABELS[s] || s}</Tag>,
    },
    { title: '实付', dataIndex: 'payAmount', width: 90, render: (v) => `¥${v}` },
    { title: '下单时间', dataIndex: 'createdAt', width: 160, render: fmtTime },
  ];

  const baseColumns: ColumnsType<AdminUserRow> = [
    {
      title: '用户编号',
      dataIndex: 'userNo',
      width: 140,
      render: (v, row) => (
        <Space size={4}>
          <span>{v}</span>
          {row.isTest ? <Tag color="orange">测试</Tag> : null}
        </Space>
      ),
    },
    {
      title: '昵称',
      dataIndex: 'nickname',
      width: 140,
      render: (v: string | null, row) => (
        <AdminPrimaryLink onClick={() => void openDetail(row.id)}>{v}</AdminPrimaryLink>
      ),
    },
    {
      title: '备注',
      dataIndex: 'hqRemark',
      width: 160,
      render: (v: string | null, row) => (
        <HqRemarkCell
          id={row.id}
          hqRemark={v}
          onSaved={(id, hqRemark) => {
            setData((prev) =>
              prev
                ? { ...prev, items: prev.items.map((u) => (u.id === id ? { ...u, hqRemark } : u)) }
                : prev,
            );
            setDetail((d) => (d && d.id === id ? { ...d, hqRemark } : d));
          }}
        />
      ),
    },
    {
      title: '手机',
      dataIndex: 'phone',
      width: 120,
      render: (v) => v || '—',
    },
    {
      title: '验手机',
      dataIndex: 'phoneVerifiedAt',
      width: 90,
      render: (v) => (v ? <Tag color="green">已验证</Tag> : <Tag color="orange">访客</Tag>),
    },
    {
      title: '微信',
      dataIndex: 'wechatVerified',
      width: 100,
      render: (v) => (v ? <Tag color="blue">微信已验证</Tag> : <Tag>未绑定</Tag>),
    },
    {
      title: '来源类型',
      dataIndex: 'sourceType',
      width: 100,
      render: (v: string) => (
        <Tag color={v === 'PROMO_CODE' ? 'blue' : v === 'ORGANIC' ? 'default' : 'purple'}>
          {USER_SOURCE_TYPE_LABELS[v as UserSourceType] || v}
        </Tag>
      ),
    },
    {
      title: '来源 ID',
      dataIndex: 'sourceRefId',
      width: 100,
      render: (v, row) => {
        if (!v) return '—';
        if (row.sourceType === 'PROMO_CODE') {
          return (
            <Link to={`/promo-codes/${v}`} onClick={(e) => e.stopPropagation()}>
              {v}
            </Link>
          );
        }
        return v;
      },
    },
    {
      title: '来源标签',
      dataIndex: 'sourceLabel',
      width: 120,
      render: (v) => v || '—',
    },
    {
      title: '关联合伙人',
      dataIndex: 'assocPartner',
      width: 180,
      render: (p: AdminUserRow['assocPartner']) => formatAssocPartner(p),
    },
    {
      title: 'deviceKey',
      dataIndex: 'deviceKey',
      render: (v) => v || '—',
    },
    {
      title: '合并',
      dataIndex: 'mergedIntoUserId',
      width: 80,
      render: (v) => (v ? <Tag color="blue">已合并</Tag> : '—'),
    },
    { title: '订单数', dataIndex: 'orderCount', width: 80 },
    {
      title: '剩余权益',
      dataIndex: 'benefitBalance',
      width: 110,
      align: 'right',
      sorter: (a, b) => Number(a.benefitBalance ?? 0) - Number(b.benefitBalance ?? 0),
      render: (v: number | undefined) =>
        Number(v ?? 0) > 0 ? (
          <Typography.Text strong style={{ color: '#cf1322' }}>
            {fmtBenefit(v)}
          </Typography.Text>
        ) : (
          <Typography.Text type="secondary">—</Typography.Text>
        ),
    },
    {
      title: '已用权益',
      dataIndex: 'benefitUsedAmount',
      width: 110,
      align: 'right',
      sorter: (a, b) => Number(a.benefitUsedAmount ?? 0) - Number(b.benefitUsedAmount ?? 0),
      render: (v: number | undefined) => (
        <Typography.Text type="secondary">{fmtBenefit(v)}</Typography.Text>
      ),
    },
    {
      title: '累计权益',
      dataIndex: 'benefitTotalAmount',
      width: 110,
      align: 'right',
      sorter: (a, b) => Number(a.benefitTotalAmount ?? 0) - Number(b.benefitTotalAmount ?? 0),
      render: (v: number | undefined) => fmtBenefit(v),
    },
    {
      title: '注册时间',
      dataIndex: 'createdAt',
      width: 170,
      render: (v) => new Date(v).toLocaleString('zh-CN'),
    },
    {
      title: '操作',
      width: 140,
      render: (_, row) => (
        <Space size="small">
          <Button type="link" size="small" onClick={() => openDetail(row.id)}>
            详情
          </Button>
          <Button type="link" size="small" onClick={() => navigate(`/logs/users?userId=${row.id}`)}>
            查看日志
          </Button>
        </Space>
      ),
    },
  ];

  const { columns, settingsButton, settingsModal } = useAdminListColumns('users', baseColumns, {
    page,
    pageSize,
  });

  return (
    <div>
      {settingsModal}
      <AdminListHeader
        title="用户监控"
        settings={settingsButton}
        actions={
          canDeleteUsers ? (
            <Button
              danger
              disabled={!selectedRowKeys.length}
              onClick={() => void openBatchDeleteModal()}
            >
              批量删除{selectedRowKeys.length ? ` (${selectedRowKeys.length})` : ''}
            </Button>
          ) : null
        }
      />
      <Form
        form={form}
        layout="vertical"
        onFinish={() => {
          setPage(1);
          const next = new URLSearchParams();
          const userId = String(form.getFieldValue('userId') || '').trim();
          const assoc = String(form.getFieldValue('assocPartnerAccountId') || '').trim();
          if (userId) next.set('userId', userId);
          if (assoc) next.set('assocPartnerAccountId', assoc);
          if (next.toString() !== searchParams.toString()) {
            setSearchParams(next, { replace: true });
          }
          void load();
        }}
      >
        <div className="admin-orders-filter-row">
          <Form.Item name="keyword" label="搜索" className="admin-orders-filter-item admin-users-filter-item--keyword">
            <Input allowClear placeholder="编号/昵称/备注/手机" />
          </Form.Item>
          <Form.Item
            name="assocPartnerAccountId"
            label="关联合伙人"
            className="admin-orders-filter-item admin-orders-filter-item--status"
          >
            <Select
              allowClear
              showSearch
              optionFilterProp="label"
              placeholder="全部"
              options={[
                { value: 'any', label: '已关联（全部）' },
                { value: ASSOC_UNBOUND, label: '未关联' },
                ...partners.map((p) => ({
                  value: p.id,
                  label: formatAssocPartner(p),
                })),
              ]}
            />
          </Form.Item>
          <Form.Item label=" " colon={false} className="admin-orders-filter-item admin-orders-filter-actions">
            <Space size={8} wrap>
              <Button type="primary" htmlType="submit">查询</Button>
              <Button
                onClick={() => {
                  form.resetFields();
                  setPage(1);
                  if (searchParams.toString()) {
                    setSearchParams({}, { replace: true });
                  } else {
                    void load();
                  }
                }}
              >
                重置
              </Button>
              <Button
                type="link"
                icon={filtersCollapsed ? <DownOutlined /> : <UpOutlined />}
                onClick={() => {
                  setFiltersCollapsed((prev) => {
                    const next = !prev;
                    try {
                      localStorage.setItem(FILTERS_COLLAPSED_KEY, next ? '1' : '0');
                    } catch {
                      /* ignore */
                    }
                    return next;
                  });
                }}
              >
                {filtersCollapsed ? '展开' : '收起'}
              </Button>
            </Space>
          </Form.Item>
        </div>

        {!filtersCollapsed ? (
          <div className="admin-orders-filter-row">
            <Form.Item name="phone" label="手机号" className="admin-orders-filter-item admin-orders-filter-item--md">
              <Input placeholder="模糊搜索" allowClear />
            </Form.Item>
            <Form.Item name="userNo" label="用户编号" className="admin-orders-filter-item admin-orders-filter-item--md">
              <Input placeholder="DK..." allowClear />
            </Form.Item>
            <Form.Item name="userId" label="用户ID" className="admin-orders-filter-item admin-orders-filter-item--sm">
              <Input placeholder="精确匹配" allowClear />
            </Form.Item>
            <Form.Item name="phoneVerified" label="验手机" className="admin-orders-filter-item admin-orders-filter-item--sm">
              <Select allowClear placeholder="全部" options={[
                { value: '1', label: '已验证' },
                { value: '0', label: '访客' },
              ]} />
            </Form.Item>
            <Form.Item name="status" label="状态" className="admin-orders-filter-item admin-orders-filter-item--sm">
              <Select allowClear placeholder="全部" options={[
                { value: 1, label: '正常' },
                { value: 0, label: '停用' },
              ]} />
            </Form.Item>
          </div>
        ) : null}
      </Form>

      <Table
        rowKey="id"
        loading={loading}
        columns={columns}
        dataSource={data?.items ?? []}
        scroll={{ x: 'max-content' }}
        rowSelection={canDeleteUsers ? {
          selectedRowKeys,
          preserveSelectedRowKeys: true,
          onChange: (keys) => setSelectedRowKeys(keys as string[]),
        } : undefined}
        pagination={{
          current: page,
          pageSize,
          total: data?.total ?? 0,
          showSizeChanger: true,
          onChange: (p, ps) => {
            setPage(p);
            setPageSize(ps);
          },
        }}
      />

      <Drawer
        title="用户详情"
        width={640}
        open={drawerOpen}
        onClose={() => setDrawerOpen(false)}
        extra={detail && canDeleteUsers ? (
          <Button danger onClick={openDeleteModal}>删除用户</Button>
        ) : undefined}
      >
        {detail && (
          <>
            <Descriptions column={1} bordered size="small">
              <Descriptions.Item label="ID">{detail.id}</Descriptions.Item>
              <Descriptions.Item label="用户编号">{detail.userNo}</Descriptions.Item>
              <Descriptions.Item label="昵称">{detail.nickname || '—'}</Descriptions.Item>
              <Descriptions.Item label="备注">{detail.hqRemark || '—'}</Descriptions.Item>
              <Descriptions.Item label="手机号">{detail.phone || '—'}</Descriptions.Item>
              <Descriptions.Item label="验手机时间">
                {detail.phoneVerifiedAt ? new Date(detail.phoneVerifiedAt).toLocaleString('zh-CN') : '未验证'}
              </Descriptions.Item>
              <Descriptions.Item label="微信验证">
                {detail.wechatVerified ? <Tag color="blue">微信已验证</Tag> : <Tag>未绑定</Tag>}
              </Descriptions.Item>
              <Descriptions.Item label="来源类型">
                <Tag color={detail.sourceType === 'PROMO_CODE' ? 'blue' : 'default'}>
                  {USER_SOURCE_TYPE_LABELS[detail.sourceType as UserSourceType] || detail.sourceType}
                </Tag>
              </Descriptions.Item>
              <Descriptions.Item label="来源 ID">
                {detail.sourceRefId ? (
                  detail.sourceType === 'PROMO_CODE' ? (
                    <Link to={`/promo-codes/${detail.sourceRefId}`}>{detail.sourceRefId}</Link>
                  ) : (
                    detail.sourceRefId
                  )
                ) : '—'}
              </Descriptions.Item>
              <Descriptions.Item label="来源标签">{detail.sourceLabel || '—'}</Descriptions.Item>
              <Descriptions.Item label="关联合伙人">
                {canEditAssoc ? (
                  <Space wrap>
                    <Select
                      allowClear
                      showSearch
                      optionFilterProp="label"
                      placeholder="未关联"
                      style={{ minWidth: 260 }}
                      value={assocPartnerId}
                      onChange={setAssocPartnerId}
                      options={partnerOptions.map((p) => ({
                        value: p.id,
                        label: `${p.companyName || p.name || p.id}${p.phone ? ` · ${p.phone}` : ''}`,
                      }))}
                    />
                    <Button type="primary" size="small" loading={savingAssoc} onClick={() => void saveAssoc()}>
                      保存关联
                    </Button>
                    <Typography.Text type="secondary">清空后保存即解绑；已支付订单佣金快照不变</Typography.Text>
                  </Space>
                ) : detail.assocPartner ? (
                  `${detail.assocPartner.companyName || detail.assocPartner.name}${
                    detail.assocPartner.phone ? ` / ${detail.assocPartner.phone}` : ''
                  }`
                ) : (
                  '—'
                )}
              </Descriptions.Item>
              <Descriptions.Item label="关联时间">
                {detail.assocBoundAt ? fmtTime(detail.assocBoundAt) : '—'}
              </Descriptions.Item>
              {detail.sourcePromo && (
                <Descriptions.Item label="推广码">
                  <Link to={`/promo-codes/${detail.sourcePromo.id}`}>
                    {detail.sourcePromo.name}（{detail.sourcePromo.code}）
                  </Link>
                </Descriptions.Item>
              )}
              <Descriptions.Item label="wxOpenId">{detail.wxOpenId || '—'}</Descriptions.Item>
              <Descriptions.Item label="wxUnionId">{detail.wxUnionId || '—'}</Descriptions.Item>
              <Descriptions.Item label="deviceKey">{detail.deviceKey || '—'}</Descriptions.Item>
              <Descriptions.Item label="合并至">
                {detail.mergedInto
                  ? `${detail.mergedInto.userNo} (${detail.mergedInto.phone || '无手机'})`
                  : '—'}
              </Descriptions.Item>
              <Descriptions.Item label="被合并访客数">{detail.mergedFromCount ?? 0}</Descriptions.Item>
              <Descriptions.Item label="订单/地址">{detail.orderCount} / {detail.addressCount}</Descriptions.Item>
              <Descriptions.Item label="好客权益">
                <Space size={16} wrap>
                  <span>
                    剩余{' '}
                    <Typography.Text strong style={{ color: '#cf1322' }}>
                      {fmtBenefit(detail.benefitBalance)}
                    </Typography.Text>
                  </span>
                  <span>已用 {fmtBenefit(detail.benefitUsedAmount)}</span>
                  <span>累计 {fmtBenefit(detail.benefitTotalAmount)}</span>
                </Space>
              </Descriptions.Item>
              <Descriptions.Item label="注册时间">
                {new Date(detail.createdAt).toLocaleString('zh-CN')}
              </Descriptions.Item>
            </Descriptions>

            <Typography.Title level={5} style={{ marginTop: 16 }}>
              行为时间线（最近 {behaviorLogs.length} 条）
            </Typography.Title>
            <Table
              size="small"
              rowKey="id"
              pagination={false}
              scroll={{ y: 200 }}
              dataSource={behaviorLogs}
              locale={{ emptyText: '暂无行为日志' }}
              columns={[
                { title: '时间', dataIndex: 'createdAt', width: 160, render: (v) => fmtTime(v) },
                {
                  title: '分类',
                  width: 100,
                  render: (_, r) => userEventCategoryLabel(r.eventName),
                },
                { title: '事件', dataIndex: 'eventName', render: (v: string) => eventNameLabel(v) },
                { title: '端', dataIndex: 'clientApp', width: 110, render: (v: string | null) => clientAppLabel(v) },
              ]}
            />

            <Typography.Title level={5} style={{ marginTop: 16 }}>
              全部订单（{detail.orders?.length ?? 0}）
            </Typography.Title>
            <Table
              size="small"
              rowKey="id"
              pagination={false}
              scroll={{ x: 520, y: 240 }}
              dataSource={detail.orders ?? []}
              columns={orderColumns}
              locale={{ emptyText: '暂无订单' }}
            />

            <Button
              type="primary"
              style={{ marginTop: 16 }}
              onClick={() => navigate(`/logs/users?userId=${detail.id}`)}
            >
              查看用户日志
            </Button>
          </>
        )}
      </Drawer>

      <Modal
        title="确认删除用户"
        open={deleteOpen}
        okText="确认删除"
        okButtonProps={{
          danger: true,
          disabled: !detail || deleteConfirm !== detail.userNo,
          loading: deleting,
        }}
        onOk={() => void confirmDelete()}
        onCancel={() => setDeleteOpen(false)}
        width={720}
        destroyOnClose
      >
        {detail && (
          <>
            <Alert
              type="error"
              showIcon
              style={{ marginBottom: 16 }}
              message="此操作不可恢复"
              description={(
                <>
                  将删除用户 <strong>{detail.userNo}</strong> 及其地址、订单、权益券、核销记录等业务数据。
                  <br />
                  用户行为日志（埋点）与第三方调用日志将<strong>保留</strong>，不随用户删除。
                </>
              )}
            />
            <Typography.Text strong>关联订单（{detail.orders?.length ?? 0} 笔）</Typography.Text>
            <Table
              size="small"
              style={{ marginTop: 8, marginBottom: 16 }}
              rowKey="id"
              pagination={false}
              scroll={{ x: 520, y: 200 }}
              dataSource={detail.orders ?? []}
              columns={orderColumns}
              locale={{ emptyText: '无订单' }}
            />
            <Typography.Paragraph type="secondary">
              请输入用户编号 <Typography.Text code>{detail.userNo}</Typography.Text> 以确认删除：
            </Typography.Paragraph>
            <Input
              value={deleteConfirm}
              placeholder={detail.userNo}
              onChange={(e) => setDeleteConfirm(e.target.value)}
            />
          </>
        )}
      </Modal>

      <Modal
        title={batchDeleteStep === 1 ? `确认批量删除（${selectedRowKeys.length} 人）` : '二次确认：删除关联业务数据'}
        open={batchDeleteOpen}
        okText={batchPreview?.hasRisk && batchDeleteStep === 1 ? '下一步' : '确认删除'}
        okButtonProps={{
          danger: batchDeleteStep === 2 || !batchPreview?.hasRisk,
          loading: batchPreviewLoading || batchDeleting,
          disabled: batchDeleteStep === 2 && !batchRiskAck,
        }}
        cancelText={batchDeleteStep === 2 ? '上一步' : '取消'}
        onOk={() => handleBatchDeleteOk()}
        onCancel={() => {
          if (batchDeleteStep === 2) {
            setBatchDeleteStep(1);
            setBatchRiskAck(false);
            return;
          }
          setBatchDeleteOpen(false);
        }}
        width={800}
        destroyOnClose
      >
        {batchPreviewLoading && (
          <Typography.Text type="secondary">正在检查关联订单与核销记录…</Typography.Text>
        )}
        {!batchPreviewLoading && batchPreview && batchDeleteStep === 1 && (
          <>
            <Alert
              type={batchPreview.hasRisk ? 'warning' : 'error'}
              showIcon
              style={{ marginBottom: 16 }}
              message={batchPreview.hasRisk ? '部分用户存在未完成订单或核销记录' : '此操作不可恢复'}
              description={batchPreview.hasRisk
                ? '标有「需关注」的用户名下有未完成订单和/或核销记录。继续后将进入二次确认，确认后将一并删除相关订单、核销记录及权益等业务数据。用户行为日志将保留。'
                : `将删除 ${batchPreview.total} 名用户及其地址、订单、权益券等业务数据。用户行为日志将保留。`}
            />
            <Table
              size="small"
              rowKey="id"
              pagination={false}
              scroll={{ x: 640, y: 320 }}
              dataSource={batchPreview.items}
              expandable={{
                rowExpandable: (row) => row.hasRisk,
                expandedRowRender: (row) => (
                  <div style={{ padding: '0 8px 8px' }}>
                    {row.unfinishedOrders.length > 0 && (
                      <>
                        <Typography.Text strong>未完成订单（{row.unfinishedOrders.length}）</Typography.Text>
                        <Table
                          size="small"
                          style={{ marginTop: 8, marginBottom: 12 }}
                          rowKey="id"
                          pagination={false}
                          dataSource={row.unfinishedOrders}
                          columns={orderColumns}
                        />
                      </>
                    )}
                    {row.redeemRecords.length > 0 && (
                      <>
                        <Typography.Text strong>核销记录（{row.redeemRecords.length}）</Typography.Text>
                        <Table
                          size="small"
                          style={{ marginTop: 8 }}
                          rowKey="id"
                          pagination={false}
                          dataSource={row.redeemRecords}
                          columns={[
                            { title: '核销单号', dataIndex: 'redeemNo', width: 160 },
                            { title: '金额', dataIndex: 'amount', width: 90, render: (v) => `¥${v}` },
                            {
                              title: '打款状态',
                              dataIndex: 'payoutStatus',
                              width: 100,
                              render: (v) => (v === 'PENDING' ? <Tag color="orange">待打款</Tag> : v === 'PAID' ? <Tag color="green">已打款</Tag> : '—'),
                            },
                            { title: '时间', dataIndex: 'createdAt', width: 160, render: fmtTime },
                          ]}
                        />
                      </>
                    )}
                  </div>
                ),
              }}
              columns={[
                { title: '用户编号', dataIndex: 'userNo', width: 120 },
                { title: '昵称', dataIndex: 'nickname', width: 100, render: (v) => v || '—' },
                { title: '手机', dataIndex: 'phone', width: 120, render: (v) => maskPhone(v) },
                {
                  title: '风险',
                  width: 200,
                  render: (_, row) => (row.hasRisk ? (
                    <Space size={4} wrap>
                      <Tag color="warning">需关注</Tag>
                      {row.unfinishedOrders.length > 0 && (
                        <Tag color="orange">未完成订单 {row.unfinishedOrders.length}</Tag>
                      )}
                      {row.redeemRecords.length > 0 && (
                        <Tag color="red">核销 {row.redeemRecords.length}</Tag>
                      )}
                    </Space>
                  ) : (
                    <Tag color="default">无关联风险</Tag>
                  )),
                },
              ]}
            />
          </>
        )}
        {!batchPreviewLoading && batchPreview && batchDeleteStep === 2 && (
          <>
            <Alert
              type="error"
              showIcon
              style={{ marginBottom: 16 }}
              message="即将删除未完成订单与核销记录"
              description={(
                <>
                  以下 <strong>{batchPreview.items.filter((i) => i.hasRisk).length}</strong> 名用户存在未完成订单或核销记录。
                  确认后将<strong>永久删除</strong>这些订单、核销记录、权益券及门店打款关联数据，且不可恢复。
                </>
              )}
            />
            <Table
              size="small"
              style={{ marginBottom: 16 }}
              rowKey="id"
              pagination={false}
              scroll={{ y: 200 }}
              dataSource={batchPreview.items.filter((i) => i.hasRisk)}
              columns={[
                { title: '用户编号', dataIndex: 'userNo', width: 120 },
                { title: '未完成订单', width: 110, render: (_, row) => row.unfinishedOrders.length },
                { title: '核销记录', width: 90, render: (_, row) => row.redeemRecords.length },
              ]}
            />
            <Checkbox checked={batchRiskAck} onChange={(e) => setBatchRiskAck(e.target.checked)}>
              我确认删除上述用户的未完成订单、核销记录及相关业务数据
            </Checkbox>
          </>
        )}
      </Modal>
    </div>
  );
}
