import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AiHardware } from '@/pages/AiHardware';
import {
  AiHardwareApiError,
  type AiHardwareAgentConfiguration,
  type AiHardwareConfigurationCatalog,
  type AiHardwareOverview,
} from '@/lib/ai-hardware';

const api = vi.hoisted(() => ({
  getAiHardwareOverview: vi.fn(),
  recoverAiHardwareCredential: vi.fn(),
  createAiHardwareAgent: vi.fn(),
  bindAiHardwareDevice: vi.fn(),
  getAiHardwareAgentConfiguration: vi.fn(),
  getAiHardwareConfigurationCatalog: vi.fn(),
  updateAiHardwareAgentConfiguration: vi.fn(),
  getAiHardwareAssignment: vi.fn(),
  updateAiHardwareAssignment: vi.fn(),
}));

vi.mock('@/lib/ai-hardware', () => {
  class MockAiHardwareApiError extends Error {
    status: number;
    code: string;
    retryable: boolean;
    retryAfterSeconds: number | null;
    operationId: string | null;

    constructor(options: { status: number; code: string; message: string; retryable?: boolean; retryAfterSeconds?: number | null; operationId?: string | null }) {
      super(options.message);
      this.name = 'AiHardwareApiError';
      this.status = options.status;
      this.code = options.code;
      this.retryable = options.retryable ?? false;
      this.retryAfterSeconds = options.retryAfterSeconds ?? null;
      this.operationId = options.operationId ?? null;
    }
  }
  return { ...api, AiHardwareApiError: MockAiHardwareApiError };
});

const agentOne = { id: 'agent-local-1', name: '客厅助手', config_revision: 4 };
const agentTwo = { id: 'agent-local-2', name: '书房助手', config_revision: 2 };
const device = { id: 'device-local-1', agent_id: agentOne.id, assignment_revision: 3 };
const activeOverview: AiHardwareOverview = { status: 'active', agents: [agentOne, agentTwo], devices: [device] };
const configuration: AiHardwareAgentConfiguration = {
  ...agentOne,
  system_prompt: '保持简洁', lang_code: 'zh-CN', language: '中文',
  asr_model_id: null, vad_model_id: null, llm_model_id: null, slm_model_id: null,
  vllm_model_id: null, tts_model_id: null, tts_voice_id: 'voice-a', tts_language: null,
  tts_volume: 10, tts_rate: 5, tts_pitch: 0, mem_model_id: null, intent_model_id: null,
  chat_history_conf: 1,
};
const catalog: AiHardwareConfigurationCatalog = {
  schema_version: 1,
  models: [
    ['ASR', 'asr-old', '旧语音识别'], ['ASR', 'asr-new', '新语音识别'],
    ['VAD', 'vad-old', '旧活动检测'], ['VAD', 'vad-new', '新活动检测'],
    ['LLM', 'llm-old', '旧语言模型'], ['LLM', 'llm-new', '新语言模型'],
    ['LLM', 'slm-new', '轻量语言模型'], ['VLLM', 'vllm-old', '旧视觉模型'],
    ['TTS', 'tts-old', '旧语音合成'], ['TTS', 'tts-new', '新语音合成'],
    ['Memory', 'mem-old', '旧记忆'], ['Memory', 'mem-new', '新记忆'],
    ['Intent', 'intent-old', '旧意图'], ['Intent', 'intent-new', '新意图'],
  ].map(([model_type, model_id, model_name]) => ({
    model_type, model_id, model_name, supports_function_call: null,
  })) as AiHardwareConfigurationCatalog['models'],
  voices: [{
    tts_model_id: 'tts-new', voice_id: 'voice-new', voice_name: '龙小夏',
    languages: ['zh-CN', 'en-US'], is_clone: false,
  }],
};

function deferred<T>() {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>((done) => { resolve = done; });
  return { promise, resolve };
}

async function openConfigurationEditor(): Promise<void> {
  const editButton = await screen.findByRole('button', { name: '编辑配置' });
  await waitFor(() => expect(editButton).toBeEnabled());
  fireEvent.click(editButton);
  await screen.findByRole('heading', { name: '编辑智能体配置' });
}

describe('AI hardware page', () => {
  beforeEach(() => {
    api.getAiHardwareOverview.mockResolvedValue(activeOverview);
    api.recoverAiHardwareCredential.mockResolvedValue({ status: 'active', agents: [], devices: [] });
    api.getAiHardwareAgentConfiguration.mockResolvedValue({ data: configuration, revision: 4 });
    api.getAiHardwareConfigurationCatalog.mockResolvedValue(catalog);
    api.createAiHardwareAgent.mockResolvedValue(agentOne);
    api.bindAiHardwareDevice.mockResolvedValue(device);
    api.updateAiHardwareAgentConfiguration.mockResolvedValue({ data: configuration, revision: 5 });
    api.getAiHardwareAssignment.mockResolvedValue({ data: device, revision: 3 });
    api.updateAiHardwareAssignment.mockResolvedValue({ data: { ...device, agent_id: agentTwo.id }, revision: 4 });
  });

  it('shows loading, disabled, and credential recovery states without exposing diagnostics', async () => {
    const pending = deferred<AiHardwareOverview>();
    api.getAiHardwareOverview.mockReturnValueOnce(pending.promise);
    const first = render(<AiHardware />);
    expect(screen.getByText('正在读取 AI 机器')).toBeInTheDocument();
    first.unmount();

    api.getAiHardwareOverview.mockRejectedValueOnce(new AiHardwareApiError({
      status: 503, code: 'AI_HARDWARE_DISABLED', message: 'internal token=do-not-show',
    }));
    const disabled = render(<AiHardware />);
    expect(await screen.findByText('AI 机器尚未启用')).toBeInTheDocument();
    expect(screen.queryByText(/do-not-show|token=/)).not.toBeInTheDocument();
    disabled.unmount();

    api.getAiHardwareOverview.mockResolvedValueOnce({ status: 'credential_recovery_required', agents: [], devices: [] });
    render(<AiHardware />);
    expect(await screen.findByText('需要恢复设备凭据')).toBeInTheDocument();
  });

  it('shows the empty CTA and creates the first agent', async () => {
    api.getAiHardwareOverview
      .mockResolvedValueOnce({ status: 'active', agents: [], devices: [] })
      .mockResolvedValueOnce(activeOverview);
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '创建智能体' }));
    fireEvent.change(screen.getByLabelText('名称'), { target: { value: '客厅助手' } });
    fireEvent.click(screen.getByRole('button', { name: '创建' }));
    await waitFor(() => expect(api.createAiHardwareAgent).toHaveBeenCalledWith('客厅助手'));
    expect((await screen.findAllByText('客厅助手')).length).toBeGreaterThan(0);
  });

  it('provisions an unprovisioned account by creating its first agent', async () => {
    api.getAiHardwareOverview
      .mockResolvedValueOnce({ status: 'unprovisioned', agents: [], devices: [] })
      .mockResolvedValueOnce(activeOverview);
    render(<AiHardware />);
    expect(await screen.findByText('创建智能体将同时开通你的机器人工作台。')).toBeInTheDocument();
    fireEvent.click(screen.getByRole('button', { name: '创建智能体' }));
    fireEvent.change(screen.getByLabelText('名称'), { target: { value: '首次智能体' } });
    fireEvent.click(screen.getByRole('button', { name: '创建' }));

    await waitFor(() => expect(api.createAiHardwareAgent).toHaveBeenCalledWith('首次智能体'));
    expect(api.getAiHardwareOverview).toHaveBeenCalledTimes(2);
  });

  it('validates activation codes and clears them after a failed binding', async () => {
    const secret = '031425';
    api.bindAiHardwareDevice.mockRejectedValueOnce(new AiHardwareApiError({
      status: 422, code: 'ai_hardware_activation_code_invalid', message: `invalid ${secret}`,
    }));
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '绑定设备' }));
    const input = screen.getByLabelText('6 位激活码');
    fireEvent.change(input, { target: { value: '12x' } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    expect(screen.getByRole('alert')).toHaveTextContent('请输入 6 位数字激活码');
    expect(api.bindAiHardwareDevice).not.toHaveBeenCalled();

    fireEvent.change(input, { target: { value: secret } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    await waitFor(() => expect(api.bindAiHardwareDevice).toHaveBeenCalledWith(secret, agentOne.id));
    await waitFor(() => expect(input).toHaveValue(''));
    expect(screen.queryByText(new RegExp(secret))).not.toBeInTheDocument();
    expect(screen.getByRole('alert')).toHaveTextContent('激活码无效');
  });

  it('sends cleared nullable configuration fields through clear_fields and never sends null', async () => {
    render(<AiHardware />);
    await openConfigurationEditor();
    fireEvent.change(screen.getByLabelText('名称'), { target: { value: '新的助手' } });
    fireEvent.change(screen.getByLabelText('系统提示'), { target: { value: '' } });
    fireEvent.change(screen.getByLabelText('声音音色'), { target: { value: '' } });
    fireEvent.change(screen.getByLabelText('音量'), { target: { value: '20' } });
    fireEvent.change(screen.getByLabelText('聊天记录'), { target: { value: '2' } });
    fireEvent.click(screen.getByRole('button', { name: '保存' }));

    await waitFor(() => expect(api.updateAiHardwareAgentConfiguration).toHaveBeenCalled());
    const [, revision, update] = api.updateAiHardwareAgentConfiguration.mock.calls[0];
    expect(revision).toBe(4);
    expect(update).toMatchObject({
      agent_name: '新的助手', tts_volume: 20, chat_history_conf: 2,
      clear_fields: expect.arrayContaining(['system_prompt', 'tts_voice_id']),
    });
    expect(Object.values(update)).not.toContain(null);
  });

  it.each(['credential_recovery_required', 'invalid'] as const)(
    'recovers credentials from the %s state and enters the returned workspace', async (status) => {
      const restored: AiHardwareOverview = { status: 'active', agents: [], devices: [] };
      api.getAiHardwareOverview.mockResolvedValueOnce({ status, agents: [], devices: [] });
      const pending = deferred<AiHardwareOverview>();
      api.recoverAiHardwareCredential.mockReturnValueOnce(pending.promise);
      render(<AiHardware />);
      const button = await screen.findByRole('button', { name: '恢复设备凭据' });
      fireEvent.click(button);
      expect(button).toBeDisabled();
      fireEvent.click(button);
      expect(api.recoverAiHardwareCredential).toHaveBeenCalledTimes(1);
      pending.resolve(restored);
      expect(await screen.findByText('创建第一个智能体')).toBeInTheDocument();
    },
  );

  it('reuses a recovery operation id after a retryable safe error', async () => {
    const operationId = '123e4567-e89b-42d3-a456-426614174000';
    api.getAiHardwareOverview.mockResolvedValueOnce({ status: 'credential_recovery_required', agents: [], devices: [] });
    api.recoverAiHardwareCredential
      .mockRejectedValueOnce(new AiHardwareApiError({
        status: 503, code: 'xiaozhi_hardware_unavailable', message: 'encrypted material=secret',
        retryable: true, operationId,
      }))
      .mockResolvedValueOnce({ status: 'active', agents: [], devices: [] });
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '恢复设备凭据' }));
    expect(await screen.findByRole('alert')).toHaveTextContent('服务暂时不可用');
    expect(screen.queryByText(/encrypted material|secret/)).not.toBeInTheDocument();
    fireEvent.click(screen.getByRole('button', { name: '恢复设备凭据' }));
    await waitFor(() => expect(api.recoverAiHardwareCredential).toHaveBeenCalledTimes(2));
    expect(api.recoverAiHardwareCredential.mock.calls[1]).toEqual([{ operationId }]);
    expect(await screen.findByText('创建第一个智能体')).toBeInTheDocument();
  });

  it('clears a stale recovery intent and refreshes when recovery is unavailable', async () => {
    const operationId = '123e4567-e89b-42d3-a456-426614174000';
    api.getAiHardwareOverview
      .mockResolvedValueOnce({ status: 'credential_recovery_required', agents: [], devices: [] })
      .mockResolvedValueOnce({ status: 'active', agents: [], devices: [] });
    api.recoverAiHardwareCredential
      .mockRejectedValueOnce(new AiHardwareApiError({
        status: 409, code: 'ai_hardware_credential_recovery_unavailable',
        message: 'provider secret', operationId,
      }))
      .mockResolvedValueOnce({ status: 'active', agents: [], devices: [] });
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '恢复设备凭据' }));

    expect(await screen.findByText('创建第一个智能体')).toBeInTheDocument();
    expect(api.getAiHardwareOverview).toHaveBeenCalledTimes(2);
    expect(screen.queryByText(/provider secret|当前状态无需或不能恢复/)).not.toBeInTheDocument();

    api.getAiHardwareOverview.mockResolvedValueOnce({ status: 'credential_recovery_required', agents: [], devices: [] });
    fireEvent.click(screen.getByRole('button', { name: '刷新 AI 机器' }));
    fireEvent.click(await screen.findByRole('button', { name: '恢复设备凭据' }));
    await waitFor(() => expect(api.recoverAiHardwareCredential).toHaveBeenCalledTimes(2));
    expect(api.recoverAiHardwareCredential.mock.calls[1]).toEqual([]);
  });

  it('edits and clears every supported advanced configuration field', async () => {
    api.getAiHardwareAgentConfiguration.mockResolvedValueOnce({
      data: {
        ...configuration,
        asr_model_id: 'asr-old', vad_model_id: 'vad-old', llm_model_id: 'llm-old',
        slm_model_id: 'slm-old', vllm_model_id: 'vllm-old', tts_model_id: 'tts-old',
        tts_language: 'zh', mem_model_id: 'mem-old', intent_model_id: 'intent-old',
      },
      revision: 4,
    });
    render(<AiHardware />);
    await openConfigurationEditor();

    const values: Record<string, string> = {
      asr_model_id: '', vad_model_id: 'vad-new', llm_model_id: '', slm_model_id: 'slm-new',
      vllm_model_id: '', tts_model_id: 'tts-new', tts_language: '', mem_model_id: 'mem-new',
      intent_model_id: '',
    };
    for (const [key, value] of Object.entries(values)) {
      fireEvent.change(document.querySelector(`#hardware-${key}`) as HTMLInputElement, { target: { value } });
    }
    fireEvent.click(screen.getByRole('button', { name: '保存' }));

    await waitFor(() => expect(api.updateAiHardwareAgentConfiguration).toHaveBeenCalled());
    const update = api.updateAiHardwareAgentConfiguration.mock.calls[0][2];
    expect(update).toMatchObject({
      vad_model_id: 'vad-new', slm_model_id: 'slm-new', tts_model_id: 'tts-new', mem_model_id: 'mem-new',
      clear_fields: expect.arrayContaining([
        'asr_model_id', 'llm_model_id', 'vllm_model_id', 'tts_language', 'intent_model_id',
      ]),
    });
    expect(Object.values(update)).not.toContain(null);
  });

  it('uses dynamic model, language, and voice choices instead of free-form IDs', async () => {
    api.getAiHardwareAgentConfiguration.mockResolvedValueOnce({
      data: { ...configuration, tts_model_id: 'tts-old', tts_voice_id: 'legacy-voice' },
      revision: 4,
    });
    render(<AiHardware />);
    await openConfigurationEditor();

    expect(api.getAiHardwareConfigurationCatalog).toHaveBeenCalledWith('tts-old');
    expect(screen.getByLabelText('语音合成 (TTS)')).toHaveValue('tts-old');
    expect(screen.getByRole('option', { name: '旧语音合成' })).toBeInTheDocument();
    expect(screen.getByRole('option', { name: '当前值（目录中不可用）：legacy-voice' })).toBeInTheDocument();

    fireEvent.change(screen.getByLabelText('语音合成 (TTS)'), { target: { value: 'tts-new' } });
    await waitFor(() => expect(api.getAiHardwareConfigurationCatalog).toHaveBeenLastCalledWith('tts-new'));
    expect(screen.getByRole('option', { name: '龙小夏' })).toBeInTheDocument();
    expect(screen.getAllByRole('option', { name: 'zh-CN' }).length).toBeGreaterThan(0);
  });

  it('uses bounded sliders while preserving nullable provider defaults', async () => {
    render(<AiHardware />);
    await openConfigurationEditor();

    const volume = screen.getByLabelText('音量');
    expect(volume).toHaveAttribute('type', 'range');
    expect(volume).toHaveAttribute('min', '-100');
    expect(volume).toHaveAttribute('max', '100');
    expect(screen.getByRole('button', { name: '音调使用默认值' })).toHaveClass('min-h-10');
    fireEvent.change(volume, { target: { value: '24' } });
    fireEvent.click(screen.getByRole('button', { name: '音调使用默认值' }));
    fireEvent.click(screen.getByRole('button', { name: '保存' }));

    await waitFor(() => expect(api.updateAiHardwareAgentConfiguration).toHaveBeenCalled());
    expect(api.updateAiHardwareAgentConfiguration.mock.calls[0][2]).toMatchObject({
      tts_volume: 24,
      clear_fields: expect.arrayContaining(['tts_pitch']),
    });
  });

  it('keeps current values when the catalog is unavailable and offers a safe retry', async () => {
    api.getAiHardwareConfigurationCatalog
      .mockRejectedValueOnce(new Error('provider token=secret'))
      .mockResolvedValueOnce(catalog);
    render(<AiHardware />);
    await openConfigurationEditor();

    expect(await screen.findByRole('alert')).toHaveTextContent('当前配置会保持不变');
    expect(screen.getByRole('option', { name: '当前值（目录中不可用）：voice-a' })).toBeInTheDocument();
    expect(screen.queryByText(/provider token|secret/)).not.toBeInTheDocument();
    expect(screen.getByRole('button', { name: '重新加载选项' })).toHaveClass('min-h-10');
    fireEvent.click(screen.getByRole('button', { name: '重新加载选项' }));
    await waitFor(() => expect(api.getAiHardwareConfigurationCatalog).toHaveBeenCalledTimes(2));
    expect(await screen.findByRole('option', { name: '旧语音合成' })).toBeInTheDocument();
  });

  it.each([
    ['AI_HARDWARE_AUTH_REQUIRED', '请先登录'],
    ['ai_hardware_unconfigured', '尚未配置'],
    ['ai_hardware_credential_recovery_required', '恢复设备凭据'],
    ['ai_hardware_activation_code_invalid', '激活码无效'],
    ['ai_hardware_device_already_bound', '设备已被绑定'],
    ['AI_HARDWARE_RATE_LIMITED', '请求过于频繁'],
    ['AI_HARDWARE_REVISION_CONFLICT', '其他位置更新'],
  ])('shows a safe recovery message for %s', async (code, expected) => {
    api.bindAiHardwareDevice.mockRejectedValueOnce(new AiHardwareApiError({
      status: 409, code, message: 'secret detail activation=031425', retryAfterSeconds: 7,
    }));
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '绑定设备' }));
    fireEvent.change(screen.getByLabelText('6 位激活码'), { target: { value: '031425' } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    expect(await screen.findByRole('alert')).toHaveTextContent(expected);
    expect(screen.queryByText(/secret detail|031425/)).not.toBeInTheDocument();
  });

  it('shows retry timing for an operation still in progress', async () => {
    api.bindAiHardwareDevice.mockRejectedValueOnce(new AiHardwareApiError({
      status: 409, code: 'ai_hardware_operation_in_progress', message: 'private', retryAfterSeconds: 7,
    }));
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '绑定设备' }));
    fireEvent.change(screen.getByLabelText('6 位激活码'), { target: { value: '031425' } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    expect(await screen.findByRole('alert')).toHaveTextContent('7 秒后重试');
  });

  it('reuses the server operation id only when retrying the same binding body', async () => {
    api.bindAiHardwareDevice
      .mockRejectedValueOnce(new AiHardwareApiError({
        status: 409, code: 'ai_hardware_operation_in_progress', message: 'private', retryable: true,
        operationId: '123e4567-e89b-42d3-a456-426614174000',
      }))
      .mockResolvedValueOnce(device);
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '绑定设备' }));
    const input = screen.getByLabelText('6 位激活码');
    fireEvent.change(input, { target: { value: '031425' } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    await waitFor(() => expect(input).toHaveValue(''));
    fireEvent.change(input, { target: { value: '031425' } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    await waitFor(() => expect(api.bindAiHardwareDevice).toHaveBeenCalledTimes(2));
    expect(api.bindAiHardwareDevice.mock.calls[1]).toEqual([
      '031425', agentOne.id, { operationId: '123e4567-e89b-42d3-a456-426614174000' },
    ]);
  });

  it('does not retain an activation code and starts a fresh operation for a different code', async () => {
    const operationId = '123e4567-e89b-42d3-a456-426614174000';
    api.bindAiHardwareDevice
      .mockRejectedValueOnce(new AiHardwareApiError({
        status: 409, code: 'ai_hardware_operation_in_progress', message: 'contains 031425',
        retryable: true, operationId,
      }))
      .mockResolvedValueOnce(device);
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '绑定设备' }));
    const input = screen.getByLabelText('6 位激活码');

    fireEvent.change(input, { target: { value: '031425' } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    await waitFor(() => expect(input).toHaveValue(''));
    expect(screen.queryByText(/031425/)).not.toBeInTheDocument();

    fireEvent.change(input, { target: { value: '654321' } });
    fireEvent.click(screen.getByRole('button', { name: '绑定' }));
    await waitFor(() => expect(api.bindAiHardwareDevice).toHaveBeenCalledTimes(2));
    expect(api.bindAiHardwareDevice.mock.calls[1]).toEqual(['654321', agentOne.id]);
    await waitFor(() => expect(input).toHaveValue(''));
    expect(screen.queryByText(/031425|654321/)).not.toBeInTheDocument();
  });

  it('keeps an unset chat history unchanged when saving without edits', async () => {
    api.getAiHardwareAgentConfiguration.mockResolvedValueOnce({
      data: { ...configuration, chat_history_conf: null },
      revision: 4,
    });
    render(<AiHardware />);
    await openConfigurationEditor();
    expect(screen.getByLabelText('聊天记录')).toHaveValue('');
    fireEvent.click(screen.getByRole('button', { name: '保存' }));

    await waitFor(() => expect(screen.queryByRole('heading', { name: '编辑智能体配置' })).not.toBeInTheDocument());
    expect(api.updateAiHardwareAgentConfiguration).not.toHaveBeenCalled();
  });

  it('clears the previous configuration while a newly selected agent is loading', async () => {
    const nextConfiguration = deferred<{ data: AiHardwareAgentConfiguration; revision: number }>();
    api.getAiHardwareAgentConfiguration
      .mockResolvedValueOnce({ data: configuration, revision: 4 })
      .mockReturnValueOnce(nextConfiguration.promise);
    render(<AiHardware />);
    expect(await screen.findByText('保持简洁')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /书房助手/ }));

    expect(screen.queryByText('保持简洁')).not.toBeInTheDocument();
    expect(screen.getByRole('button', { name: '编辑配置' })).toBeDisabled();
    expect(screen.getByText('正在读取配置')).toBeInTheDocument();
  });

  it('stops loading and lets the user retry after configuration loading fails', async () => {
    api.getAiHardwareOverview.mockResolvedValueOnce({
      status: 'active',
      agents: [agentOne],
      devices: [device],
    });
    api.getAiHardwareAgentConfiguration
      .mockRejectedValueOnce(new AiHardwareApiError({
        status: 502,
        code: 'xiaozhi_hardware_unavailable',
        message: 'private provider details',
        retryable: true,
      }))
      .mockResolvedValueOnce({ data: configuration, revision: 4 });

    render(<AiHardware />);

    expect(await screen.findByRole('alert')).toHaveTextContent('无法读取智能体配置');
    expect(screen.queryByText('正在读取配置')).not.toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: '重试读取配置' }));
    await waitFor(() => expect(api.getAiHardwareAgentConfiguration).toHaveBeenCalledTimes(2));
    expect(await screen.findByText('保持简洁')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: '编辑配置' })).toBeEnabled();
  });

  it('loads the current assignment revision before reassigning a device', async () => {
    render(<AiHardware />);
    fireEvent.click(await screen.findByRole('button', { name: '重新指派' }));
    expect(await screen.findByRole('heading', { name: '重新指派设备' })).toBeInTheDocument();
    fireEvent.change(screen.getByLabelText('智能体'), { target: { value: agentTwo.id } });
    fireEvent.click(screen.getByRole('button', { name: '确认指派' }));
    await waitFor(() => expect(api.updateAiHardwareAssignment).toHaveBeenCalledWith(device.id, 3, agentTwo.id));
  });

  it('rebases only locally edited fields after a configuration conflict', async () => {
    const freshConfiguration = {
      ...configuration,
      config_revision: 5,
      language: 'English',
    };
    api.getAiHardwareAgentConfiguration
      .mockResolvedValueOnce({ data: configuration, revision: 4 })
      .mockResolvedValueOnce({ data: freshConfiguration, revision: 5 });
    api.updateAiHardwareAgentConfiguration.mockRejectedValueOnce(new AiHardwareApiError({
      status: 409, code: 'ai_hardware_revision_conflict', message: 'server details',
    }));
    render(<AiHardware />);
    await openConfigurationEditor();
    fireEvent.change(screen.getByLabelText('系统提示'), { target: { value: '保留的本地提示' } });
    fireEvent.click(screen.getByRole('button', { name: '保存' }));

    expect(await screen.findByRole('alert')).toHaveTextContent('内容已在其他位置更新');
    expect(screen.getByLabelText('系统提示')).toHaveValue('保留的本地提示');
    expect(screen.getByLabelText('语言')).toHaveValue('English');
    expect(api.getAiHardwareAgentConfiguration).toHaveBeenCalledTimes(2);

    fireEvent.click(screen.getByRole('button', { name: '保存' }));
    await waitFor(() => expect(api.updateAiHardwareAgentConfiguration).toHaveBeenCalledTimes(2));
    expect(api.updateAiHardwareAgentConfiguration.mock.calls[1]).toEqual([
      agentOne.id,
      5,
      { system_prompt: '保留的本地提示' },
    ]);
  });
});
