import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import { createMemoryHistory, createRouter } from 'vue-router'

import zhCN from '@/i18n/locales/zh-CN'
import ReservationTaskDetailPanel from '@/components/reservation/ReservationTaskDetailPanel.vue'
import { useAuthStore } from '@/stores/authStore'
import type {
  ReservationManualReviewConversionResult,
  ReservationManualReviewResolutionResult,
  ReservationOrderListItem,
  ReservationOrderListResult,
  ReservationTaskAuditLogResult,
  ReservationTaskDetailResult,
  ReservationTaskFieldResult,
  ReservationTaskPayloadMutationResult,
} from '@/types/reservation'

vi.mock('@/services/reservationService', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@/services/reservationService')>()
  return {
    ...actual,
    fetchReservationTaskDetail: vi.fn(),
    fetchReservationTaskAudits: vi.fn(),
    fetchReservationOrders: vi.fn(),
    convertReservationManualReviewTask: vi.fn(),
    resolveReservationManualReviewTask: vi.fn(),
    saveReservationTaskDraft: vi.fn(),
    confirmReservationTask: vi.fn(),
  }
})

const service = await import('@/services/reservationService')

function createTaskDetail(overrides: Partial<ReservationTaskDetailResult> = {}): ReservationTaskDetailResult {
  const detail: ReservationTaskDetailResult = {
    task_id: '10001',
    order_id: '20001',
    source_message_id: '30001',
    source_subject: 'Booking Request',
    source_sender_summary: 'guest@example.test',
    source_received_at: '2026-07-08T03:00:00Z',
    external_conversation_id: 'thread-30001',
    conversation_message_count: 2,
    system_task_type: 'NEW_BOOKING',
    task_card_type: 'NEW_BOOKING',
    task_status: 'PENDING_CONFIRM',
    field_contract_version: '20260708-3.0',
    draft_payload: null,
    confirmed_payload: null,
    availability: {
      blocked: false,
      read_only: false,
      editable: true,
      confirmable: true,
      executable: false,
      blocked_by_task_id: null,
      blocked_reason: null,
    },
    fields: [],
    opera_operations: [],
  }
  return {
    ...detail,
    ...overrides,
  }
}

function createSourceMessageOnlyTaskDetail(
  overrides: Partial<ReservationTaskDetailResult> = {},
): ReservationTaskDetailResult {
  return createTaskDetail({
    result_type: 'source_message_review_notification',
    ai_task_type: 'Message Notification',
    task_subtype: 'S10',
    route_code: 'S10',
    system_process_category: 'SOURCE_MESSAGE_NOTIFICATION',
    system_task_type: 'MESSAGE_NOTIFICATION',
    task_card_type: 'MESSAGE_NOTIFICATION',
    task_status: 'COMPLETED',
    fields: [],
    opera_operations: [],
    availability: {
      blocked: false,
      read_only: true,
      editable: false,
      confirmable: false,
      executable: false,
      blocked_by_task_id: null,
      blocked_reason: null,
    },
    source_message_only_result: {
      entry_result_code: 'S10',
      entry_result_meaning: 'PURE_INFORMATION',
      entry_result_description: '纯信息类邮件',
      entry_result_source_message_id: '30001',
      result_type: 'source_message_review_notification',
      route_code: 'S10',
      agent_assessment: {
        intent: 'informational',
      },
      notification: {
        current_message_summary: '仅同步信息，不需要订单动作。',
      },
      manual_review: null,
      raw_answer: 'S10,30001',
    },
    ...overrides,
  })
}

function createMutationResult(taskStatus = 'PENDING_CONFIRM'): ReservationTaskPayloadMutationResult {
  return {
    task_id: '10001',
    order_id: '20001',
    task_status: taskStatus,
    draft_payload: {},
    confirmed_payload: taskStatus === 'READY' ? {} : null,
    opera_operations: [],
  }
}

function createManualReviewConversionResult(
  overrides: Partial<ReservationManualReviewConversionResult> = {},
): ReservationManualReviewConversionResult {
  return {
    task_id: '10001',
    order_id: '20001',
    original_order_id: '20001',
    system_task_type: 'NEW_BOOKING',
    task_card_type: 'NEW_BOOKING',
    task_status: 'PENDING_CONFIRM',
    original_order_logic_deleted: false,
    ...overrides,
  }
}

function createManualReviewResolutionResult(
  overrides: Partial<ReservationManualReviewResolutionResult> = {},
): ReservationManualReviewResolutionResult {
  return {
    task_id: '10001',
    order_id: '20001',
    task_status: 'READY',
    review_status: 'RESOLVED',
    review_resolution: {
      confirmed_order_id: '20001',
      field_overrides: [
        {
          field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
          field_path: 'extracted_fields.room_items.0.pms_room_type_code',
          legacy_field_path: 'extracted_fields.pms_room_type_code',
          value: 'RM3',
        },
      ],
    },
    confirmed_payload: {
      field_values: {
        'extracted_fields.room_items.0.pms_room_type_code': 'RM3',
      },
      legacy_field_values: {
        'extracted_fields.pms_room_type_code': 'RM3',
      },
    },
    opera_operations: [],
    ...overrides,
  }
}

function createOrderCandidate(overrides: Partial<ReservationOrderListItem> = {}): ReservationOrderListItem {
  return {
    order_id: '20002',
    hotel_id: 'HOTEL-TEST',
    order_status: 'ACTIVE',
    display_order_key: 'GRP-001',
    temporary_order_no: null,
    confirmation_number: 'CNF-001',
    group_code: 'GRP-001',
    display_name: 'GRP-001',
    open_task_count: 1,
    next_processable_task_id: null,
    created_at: '2026-07-08T03:00:00Z',
    updated_at: '2026-07-08T03:10:00Z',
    ...overrides,
  }
}

function createOrderCandidateResult(items: ReservationOrderListItem[]): ReservationOrderListResult {
  return {
    items,
    page: {
      page_num: 1,
      page_size: 8,
      total: items.length,
    },
  }
}

function createAudit(auditId: string, action: string): ReservationTaskAuditLogResult {
  return {
    audit_id: auditId,
    order_id: '20001',
    task_id: '10001',
    operation_id: null,
    actor_type: 'USER',
    actor_id: 'andy',
    action,
    reason: null,
    before_snapshot: null,
    after_snapshot: null,
    occurred_at: '2026-07-08T03:00:00Z',
  }
}

function createOperaOperation(): ReservationTaskDetailResult['opera_operations'][number] {
  return {
    operation_id: 'op-1',
    task_id: '10001',
    operation_sequence: 1,
    operation_code: 'CREATE_RESERVATION',
    operation_name: 'Create reservation',
    operation_status: 'PENDING',
    attempt_count: 0,
    last_error_message: null,
    attempts: [],
  }
}

function createRequiredField(overrides: Partial<ReservationTaskFieldResult> = {}): ReservationTaskFieldResult {
  return {
    row_number: 1,
    card_name: 'New Booking',
    display_area: '客人信息',
    field_path: 'guest.name',
    display_name: '客人姓名',
    visible: 'Y',
    editable: 'Y',
    input_editable: 'Y',
    select_editable: 'N',
    date_picker: 'N',
    number_input: 'N',
    file_display: 'N',
    table_editable: 'N',
    enum_options: null,
    required_rule: 'Y',
    display_condition: null,
    validation_rule: null,
    write_path: null,
    opera_write_participation: 'N',
    opera_parameter_mapping: null,
    notes: null,
    value: '',
    ...overrides,
  }
}

function createAuthPayload(permissions: string[]) {
  return {
    access_token: 'session-token',
    token_type: 'Bearer',
    expires_at: '2026-07-09T12:00:00Z',
    user: {
      id: '1900000000000000001',
      username: 'admin',
      display_name: '系统管理员',
      super_admin: true,
    },
    default_hotel_id: 'HOTEL-TEST',
    hotels: [
      {
        hotel_id: 'HOTEL-TEST',
        hotel_name: '测试酒店',
        time_zone: 'Asia/Bangkok',
        default_hotel: true,
      },
    ],
    permissions,
    menus: [],
  }
}

const defaultTaskPermissions = [
  'RESERVATION_TASK_READ',
  'RESERVATION_TASK_EDIT',
  'RESERVATION_TASK_CONFIRM',
  'RESERVATION_OPERA_SIM_EXECUTE',
  'RESERVATION_AUDIT_READ',
]

async function mountPanel(permissions = defaultTaskPermissions) {
  const i18n = createI18n({
    legacy: false,
    locale: 'zh-CN',
    messages: {
      'zh-CN': zhCN,
    },
  })
  const router = createRouter({
    history: createMemoryHistory(),
    routes: [
      { path: '/reservation/tasks/:taskId', component: { template: '<div />' } },
      { path: '/reservation/orders/:orderId', component: { template: '<div />' } },
      { path: '/reservation/source-messages/:sourceMessageId/conversation', component: { template: '<div />' } },
    ],
  })
  const pinia = createPinia()
  setActivePinia(pinia)
  useAuthStore().applyLoginResult(createAuthPayload(permissions))
  await router.push('/reservation/tasks/10001')
  await router.isReady()

  const wrapper = mount(ReservationTaskDetailPanel, {
    props: {
      taskId: '10001',
    },
    global: {
      plugins: [i18n, router, pinia],
      stubs: {
        RouterLink: {
          template: '<a><slot /></a>',
        },
      },
    },
  })
  await flushPromises()
  return wrapper
}

function findButton(wrapper: VueWrapper, label: string) {
  const button = wrapper.findAll('button').find((item) => item.text().includes(label))
  if (!button) {
    throw new Error(`Button ${label} was not found.`)
  }
  return button
}

describe('ReservationTaskDetailPanel', () => {
  beforeEach(() => {
    vi.mocked(service.fetchReservationTaskDetail).mockReset()
    vi.mocked(service.fetchReservationTaskAudits).mockReset()
    vi.mocked(service.fetchReservationOrders).mockReset()
    vi.mocked(service.convertReservationManualReviewTask).mockReset()
    vi.mocked(service.resolveReservationManualReviewTask).mockReset()
    vi.mocked(service.saveReservationTaskDraft).mockReset()
    vi.mocked(service.confirmReservationTask).mockReset()
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(createTaskDetail())
    vi.mocked(service.fetchReservationTaskAudits).mockResolvedValue({
      task_id: '10001',
      items: [],
    })
    vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderCandidateResult([]))
    vi.mocked(service.convertReservationManualReviewTask).mockResolvedValue(createManualReviewConversionResult())
    vi.mocked(service.resolveReservationManualReviewTask).mockResolvedValue(createManualReviewResolutionResult())
    sessionStorage.clear()
  })

  it('shows draft save errors instead of leaving an unhandled rejection', async () => {
    vi.mocked(service.saveReservationTaskDraft).mockRejectedValue(new Error('Draft save failed'))

    const wrapper = await mountPanel()
    await findButton(wrapper, '保存草稿').trigger('click')
    await flushPromises()

    expect(wrapper.text()).toContain('Draft save failed')
    expect(findButton(wrapper, '保存草稿').attributes('disabled')).toBeUndefined()
  })

  it('shows confirm errors instead of leaving an unhandled rejection', async () => {
    vi.mocked(service.confirmReservationTask).mockRejectedValue(new Error('Confirm failed'))

    const wrapper = await mountPanel()
    await findButton(wrapper, '确认').trigger('click')
    await flushPromises()

    expect(wrapper.text()).toContain('Confirm failed')
    expect(findButton(wrapper, '确认').attributes('disabled')).toBeUndefined()
  })

  it('blocks confirmation when frontend field validation fails', async () => {
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
      createTaskDetail({
        fields: [createRequiredField()],
      }),
    )

    const wrapper = await mountPanel()
    await findButton(wrapper, '确认').trigger('click')
    await flushPromises()

    expect(service.confirmReservationTask).not.toHaveBeenCalled()
    expect(wrapper.text()).toContain('请先修正字段')
    expect(wrapper.text()).toContain('客人姓名为必填项')
  })

  it('keeps remaining validation errors and clears the summary after fields are fixed', async () => {
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
      createTaskDetail({
        fields: [
          createRequiredField(),
          createRequiredField({
            field_path: 'room.type',
            display_name: '房型',
          }),
        ],
      }),
    )

    const wrapper = await mountPanel()
    await findButton(wrapper, '确认').trigger('click')
    await flushPromises()

    expect(wrapper.text()).toContain('请先修正字段')
    expect(wrapper.text()).toContain('客人姓名为必填项')
    expect(wrapper.text()).toContain('房型为必填项')

    const textareas = wrapper.findAll('textarea')
    await textareas[0]!.setValue('王建国')
    await flushPromises()

    expect(wrapper.text()).toContain('请先修正字段')
    expect(wrapper.text()).not.toContain('客人姓名为必填项')
    expect(wrapper.text()).toContain('房型为必填项')

    await textareas[1]!.setValue('豪华 Q1A')
    await flushPromises()

    expect(wrapper.text()).not.toContain('请先修正字段')
    expect(wrapper.text()).not.toContain('客人姓名为必填项')
    expect(wrapper.text()).not.toContain('房型为必填项')
  })

  it('hydrates code-v1 legacy payload values but saves future drafts with P0 field paths', async () => {
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
      createTaskDetail({
        field_contract_version: 'code-v1',
        draft_payload: {
          field_values: {
            'extracted_fields.room_items.0.pms_room_type_code': 'P0-SHOULD-NOT-WIN',
          },
          legacy_field_values: {
            'extracted_fields.pms_room_type_code': 'LEGACY-RM',
          },
        },
        fields: [
          createRequiredField({
            field_path: 'extracted_fields.room_items.0.pms_room_type_code',
            field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
            legacy_field_path: 'extracted_fields.pms_room_type_code',
            display_name: 'PMS 房型代码',
            value: null,
          }),
        ],
      }),
    )
    vi.mocked(service.saveReservationTaskDraft).mockResolvedValue(createMutationResult())

    const wrapper = await mountPanel()
    const pmsInput = wrapper.find('textarea')

    expect((pmsInput.element as HTMLTextAreaElement).value).toBe('LEGACY-RM')
    expect(wrapper.text()).not.toContain('P0-SHOULD-NOT-WIN')

    await pmsInput.setValue('RM4')
    await findButton(wrapper, '保存草稿').trigger('click')
    await flushPromises()

    expect(service.saveReservationTaskDraft).toHaveBeenCalledWith('10001', {
      field_values: {
        'extracted_fields.room_items.0.pms_room_type_code': 'RM4',
      },
    })
  })

  it('refreshes audit records after saving a draft', async () => {
    vi.mocked(service.fetchReservationTaskAudits)
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-1', 'TASK_LOADED')],
      })
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-2', 'DRAFT_SAVED')],
      })
    vi.mocked(service.saveReservationTaskDraft).mockResolvedValue(createMutationResult())

    const wrapper = await mountPanel()
    expect(wrapper.text()).toContain('任务读取')

    await findButton(wrapper, '保存草稿').trigger('click')
    await flushPromises()

    expect(service.fetchReservationTaskAudits).toHaveBeenCalledTimes(2)
    expect(wrapper.text()).toContain('草稿保存')
    expect(wrapper.text()).not.toContain('任务读取')
    expect(wrapper.text()).not.toContain('DRAFT_SAVED')
    expect(wrapper.text()).not.toContain('TASK_LOADED')
  })

  it('saves only editable active fields to match backend task field validation', async () => {
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
      createTaskDetail({
        fields: [
          createRequiredField({
            field_path: 'visible_reason',
            display_name: '生成原因',
            editable: 'N',
            input_editable: 'N',
            required_rule: 'Y',
            value: 'AI extracted this task.',
          }),
          createRequiredField({
            field_path: 'case_keys.group_code',
            display_name: 'Group Code',
            display_condition: '对象为Group Block或Allotment时展示',
            value: 'GRP-001',
          }),
          createRequiredField({
            field_path: 'case_keys.confirmation_number',
            display_name: 'Confirmation No.',
            display_condition: '对象为FIT Reservation时展示',
            value: null,
          }),
          createRequiredField({
            field_path: 'extracted_fields.booking_object_type',
            display_name: '订单对象类型',
            select_editable: 'Y',
            input_editable: 'N',
            enum_options: 'Group Block, FIT Reservation',
            value: 'Group Block',
          }),
        ],
      }),
    )
    vi.mocked(service.saveReservationTaskDraft).mockResolvedValue(createMutationResult())

    const wrapper = await mountPanel()
    await findButton(wrapper, '保存草稿').trigger('click')
    await flushPromises()

    expect(service.saveReservationTaskDraft).toHaveBeenCalledWith(
      '10001',
      {
        field_values: {
          'case_keys.group_code': 'GRP-001',
          'extracted_fields.booking_object_type': 'Group Block',
        },
      },
    )
  })

  it('opens the source conversation by source message id without external conversation id', async () => {
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
      createTaskDetail({
        external_conversation_id: null,
      }),
    )

    const wrapper = await mountPanel()

    expect(wrapper.text()).toContain('查看邮件会话')
    expect(wrapper.text()).not.toContain('接口待补')
  })

  it('renders source-message-only entry result without editable or OPERA actions', async () => {
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(createSourceMessageOnlyTaskDetail())

    const wrapper = await mountPanel()

    expect(wrapper.text()).toContain('来源消息只读')
    expect(wrapper.text()).toContain('来源消息入口结果')
    expect(wrapper.text()).toContain('该任务仅用于展示来源消息入口结果')
    expect(wrapper.text()).toContain('S10')
    expect(wrapper.text()).toContain('PURE_INFORMATION')
    expect(wrapper.text()).toContain('纯信息类邮件')
    expect(wrapper.text()).toContain('S10,30001')
    expect(wrapper.text()).toContain('来源消息通知')
    expect(wrapper.text()).toContain('查看邮件会话')
    expect(wrapper.text()).not.toContain('查看订单')
    expect(wrapper.text()).not.toContain('保存草稿')
    expect(wrapper.text()).not.toContain('确认任务')
    expect(wrapper.text()).not.toContain('人工转换')
    expect(wrapper.text()).not.toContain('OPERA 模拟操作')
    expect(wrapper.text()).not.toContain('确认任务后生成 OPERA 模拟操作')
    expect(wrapper.find('button[title="执行"]').exists()).toBe(false)
    expect(wrapper.find('button[title="重试"]').exists()).toBe(false)
  })

  it('renders R08 parent group manual review with identity candidates on the same task card', async () => {
    vi.mocked(service.fetchReservationTaskDetail)
      .mockResolvedValueOnce(
        createTaskDetail({
          result_type: 'manual_review',
          ai_task_type: 'Cancel Allotment',
          task_subtype: 'cancel_allotment_control_block',
          route_code: 'R08_CANCEL_ALLOTMENT_CONTROL_BLOCK_REVIEW',
          system_process_category: 'BUSINESS_TASK',
          system_task_type: 'CANCEL_BOOKING',
          task_card_type: 'CANCEL_ALLOTMENT',
          task_status: 'BLOCKED',
          review_status: 'PENDING',
          manual_review: {
            reason_code: 'target_object_unclear',
            visible_reason: 'Parent Group 的 group_code 与 block_code 原始候选冲突，请人工确认目标。',
            missing_fields: ['/case_keys/group_code', '/case_keys/block_code'],
            blocking_points: ['Parent Group identity cannot be safely normalized.'],
            conflicting_points: ['PARENT-2608-CONFLICT-A', 'PARENT-2608-CONFLICT-B'],
            suggested_human_actions: ['confirm_parent_group_identity'],
            evidence_to_check: ['parent_identity_candidates'],
          },
          context_used: {
            parent_identity_candidates: [
              {
                field: 'group_code',
                value: 'PARENT-2608-CONFLICT-A',
                evidence_source: 'subject',
              },
              {
                field: 'block_code',
                value: 'PARENT-2608-CONFLICT-B',
                evidence_source: 'attachment',
                normalized_value: 'PARENT-2608-CONFLICT-B',
              },
              {
                candidate_field: 'parent_group_code',
                raw_value: 'PARENT-2608-CONFLICT-C',
                origin: 'email_body',
              },
            ],
          },
          availability: {
            blocked: true,
            read_only: true,
            editable: false,
            confirmable: false,
            executable: false,
            blocked_by_task_id: null,
            blocked_reason: 'MANUAL_REVIEW_REQUIRED',
          },
          fields: [
            createRequiredField({
              field_path: 'case_keys.group_code',
              field_pointer: '/case_keys/group_code',
              display_name: 'Parent Group 团队号',
              value: null,
            }),
            createRequiredField({
              field_path: 'case_keys.block_code',
              field_pointer: '/case_keys/block_code',
              display_name: 'Parent Group Block Code',
              value: null,
            }),
          ],
        }),
      )
      .mockResolvedValueOnce(
        createTaskDetail({
          result_type: 'manual_review',
          ai_task_type: 'Cancel Allotment',
          task_subtype: 'cancel_allotment_control_block',
          route_code: 'R08_CANCEL_ALLOTMENT_CONTROL_BLOCK_REVIEW',
          system_process_category: 'BUSINESS_TASK',
          system_task_type: 'CANCEL_BOOKING',
          task_card_type: 'CANCEL_ALLOTMENT',
          task_status: 'READY',
          review_status: 'RESOLVED',
          review_resolution: {
            confirmed_order_id: '20001',
            field_overrides: [
              {
                field_pointer: '/case_keys/group_code',
                field_path: 'case_keys.group_code',
                value: 'PARENT-2608-CONFLICT-A',
              },
              {
                field_pointer: '/case_keys/block_code',
                field_path: 'case_keys.block_code',
                value: 'PARENT-2608-CONFLICT-A',
              },
            ],
          },
          availability: {
            blocked: false,
            read_only: false,
            editable: false,
            confirmable: false,
            executable: true,
            blocked_by_task_id: null,
            blocked_reason: null,
          },
          opera_operations: [createOperaOperation()],
        }),
      )
    vi.mocked(service.fetchReservationTaskAudits)
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-1', 'TASK_LOADED')],
      })
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-2', 'MANUAL_REVIEW_RESOLVED')],
      })

    const wrapper = await mountPanel()

    expect(wrapper.text()).toContain('Parent Group')
    expect(wrapper.text()).toContain('取消 Parent Group 配额')
    expect(wrapper.text()).toContain('取消 Parent Group 整块配额')
    expect(wrapper.text()).toContain('R08_CANCEL_ALLOTMENT_CONTROL_BLOCK_REVIEW')
    expect(wrapper.text()).toContain('人工复核证据')
    expect(wrapper.text()).toContain('target_object_unclear')
    expect(wrapper.text()).toContain('Parent Group 的 group_code 与 block_code 原始候选冲突')
    expect(wrapper.text()).toContain('PARENT-2608-CONFLICT-A')
    expect(wrapper.text()).toContain('PARENT-2608-CONFLICT-B')
    expect(wrapper.text()).toContain('parent_group_code')
    expect(wrapper.text()).toContain('PARENT-2608-CONFLICT-C')
    expect(wrapper.text()).toContain('subject')
    expect(wrapper.text()).toContain('attachment')
    expect(wrapper.text()).toContain('email_body')
    expect(wrapper.text()).toContain('normalized_value')
    expect(wrapper.text()).not.toContain('适配契约异常')
    expect(wrapper.text()).not.toContain('人工转换')
    expect(wrapper.text()).not.toContain('保存草稿')
    expect(wrapper.text()).not.toContain('确认任务')

    await wrapper.find('input[name="manual_resolution_field_override_0"]').setValue('PARENT-2608-CONFLICT-A')
    await wrapper.find('input[name="manual_resolution_field_override_1"]').setValue('PARENT-2608-CONFLICT-A')
    await wrapper.find('textarea[name="manual_resolution_reason"]').setValue('确认 Parent Group identity')
    await findButton(wrapper, '提交复核结果').trigger('click')
    await flushPromises()

    expect(service.resolveReservationManualReviewTask).toHaveBeenCalledWith('10001', {
      confirmed_order_id: '20001',
      reason: '确认 Parent Group identity',
      field_overrides: [
        {
          field_pointer: '/case_keys/group_code',
          field_path: 'case_keys.group_code',
          value: 'PARENT-2608-CONFLICT-A',
        },
        {
          field_pointer: '/case_keys/block_code',
          field_path: 'case_keys.block_code',
          value: 'PARENT-2608-CONFLICT-A',
        },
      ],
    })
    expect(wrapper.text()).toContain('人工复核已解阻')
  })

  it('resolves type-known manual review on the same task card', async () => {
    vi.mocked(service.fetchReservationTaskDetail)
      .mockResolvedValueOnce(
        createTaskDetail({
          result_type: 'manual_review',
          ai_task_type: 'NEW_BOOKING',
          task_subtype: 'NEW_BOOKING',
          route_code: 'NEW_BOOKING_MANUAL_REVIEW',
          system_process_category: 'BUSINESS_TASK',
          task_status: 'BLOCKED',
          review_status: 'PENDING',
          manual_review: {
            reason_code: 'missing_room_type_code',
            missing_fields: ['/extracted_fields/room_items/0/pms_room_type_code'],
            review_instruction: '请补充 PMS 房型代码。',
          },
          availability: {
            blocked: true,
            read_only: true,
            editable: false,
            confirmable: false,
            executable: false,
            blocked_by_task_id: null,
            blocked_reason: 'MANUAL_REVIEW_REQUIRED',
          },
          fields: [
            createRequiredField({
              field_path: 'extracted_fields.room_items.0.pms_room_type_code',
              field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
              legacy_field_path: 'extracted_fields.pms_room_type_code',
              display_name: 'PMS 房型代码',
              editable: 'N',
              input_editable: 'N',
              value: null,
            }),
          ],
        }),
      )
      .mockResolvedValueOnce(
        createTaskDetail({
          result_type: 'manual_review',
          ai_task_type: 'NEW_BOOKING',
          task_subtype: 'NEW_BOOKING',
          route_code: 'NEW_BOOKING_MANUAL_REVIEW',
          system_process_category: 'BUSINESS_TASK',
          task_status: 'READY',
          review_status: 'RESOLVED',
          review_resolution: {
            confirmed_order_id: '20001',
            field_overrides: [
              {
                field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
                field_path: 'extracted_fields.room_items.0.pms_room_type_code',
                legacy_field_path: 'extracted_fields.pms_room_type_code',
                value: 'RM3',
              },
            ],
          },
          availability: {
            blocked: false,
            read_only: false,
            editable: false,
            confirmable: false,
            executable: true,
            blocked_by_task_id: null,
            blocked_reason: null,
          },
          opera_operations: [createOperaOperation()],
        }),
      )
    vi.mocked(service.fetchReservationTaskAudits)
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-1', 'TASK_LOADED')],
      })
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-2', 'MANUAL_REVIEW_RESOLVED')],
      })

    const wrapper = await mountPanel()

    expect(wrapper.text()).toContain('路由与处理信息')
    expect(wrapper.text()).toContain('系统任务类型')
    expect(wrapper.text()).toContain('任务卡类型')
    expect(wrapper.text()).toContain('NEW_BOOKING_MANUAL_REVIEW')
    expect(wrapper.text()).toContain('人工复核解阻')
    expect(wrapper.text()).toContain('/extracted_fields/room_items/0/pms_room_type_code')
    expect(wrapper.text()).toContain('PMS 房型代码')
    expect(wrapper.text()).not.toContain('人工转换')
    expect(wrapper.text()).not.toContain('保存草稿')
    expect(wrapper.text()).not.toContain('确认任务')
    expect(wrapper.text()).not.toContain('OPERA 模拟操作')

    await wrapper.find('input[name="manual_resolution_field_override_0"]').setValue('RM3')
    await wrapper.find('textarea[name="manual_resolution_reason"]').setValue('确认 PMS 房型代码')
    await findButton(wrapper, '提交复核结果').trigger('click')
    await flushPromises()

    expect(service.resolveReservationManualReviewTask).toHaveBeenCalledWith('10001', {
      confirmed_order_id: '20001',
      reason: '确认 PMS 房型代码',
      field_overrides: [
        {
          field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
          field_path: 'extracted_fields.room_items.0.pms_room_type_code',
          value: 'RM3',
        },
      ],
    })
    expect(service.fetchReservationTaskDetail).toHaveBeenCalledTimes(2)
    expect(service.fetchReservationTaskAudits).toHaveBeenCalledTimes(2)
    expect(wrapper.text()).toContain('人工复核已解阻')
    expect(wrapper.text()).toContain('人工复核解阻')
    expect(wrapper.text()).toContain('OPERA 模拟操作')
    expect(wrapper.text()).toContain('CREATE_RESERVATION')
    expect(wrapper.find('button[title="执行"]').exists()).toBe(true)
    expect(wrapper.text()).not.toContain('MANUAL_REVIEW_RESOLVED')
  })

  it('converts a manual review task to a new booking without a target order', async () => {
    vi.mocked(service.fetchReservationTaskDetail)
      .mockResolvedValueOnce(
        createTaskDetail({
          system_task_type: 'MANUAL_REVIEW',
          task_card_type: 'FALLBACK_REVIEW',
        }),
      )
      .mockResolvedValueOnce(
        createTaskDetail({
          system_task_type: 'NEW_BOOKING',
          task_card_type: 'NEW_BOOKING',
        }),
      )
    vi.mocked(service.fetchReservationTaskAudits)
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-1', 'TASK_LOADED')],
      })
      .mockResolvedValueOnce({
        task_id: '10001',
        items: [createAudit('audit-2', 'MANUAL_REVIEW_CONVERT')],
      })

    const wrapper = await mountPanel()
    expect(wrapper.text()).toContain('人工转换')
    expect(wrapper.text()).not.toContain('未知任务类型')

    await wrapper.find('select[name="manual_target_task_type"]').setValue('NEW_BOOKING')
    await wrapper.find('textarea[name="manual_conversion_reason"]').setValue('识别为新预订')
    await findButton(wrapper, '转换任务').trigger('click')
    await flushPromises()

    expect(service.convertReservationManualReviewTask).toHaveBeenCalledWith('10001', {
      target_task_type: 'NEW_BOOKING',
      reason: '识别为新预订',
    })
    expect(service.fetchReservationTaskDetail).toHaveBeenCalledTimes(2)
    expect(service.fetchReservationTaskAudits).toHaveBeenCalledTimes(2)
    expect(wrapper.text()).toContain('人工转换已完成')
    expect(wrapper.text()).toContain('人工转换')
    expect(wrapper.text()).not.toContain('MANUAL_REVIEW_CONVERT')
  })

  it('requires a non-deleted target order when converting manual review to update booking', async () => {
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
      createTaskDetail({
        system_task_type: 'MANUAL_REVIEW',
        task_card_type: 'FALLBACK_REVIEW',
      }),
    )
    vi.mocked(service.fetchReservationOrders).mockResolvedValue(
      createOrderCandidateResult([
        createOrderCandidate({
          order_id: '20002',
          display_order_key: 'GRP-ACTIVE',
          group_code: 'GRP-ACTIVE',
          confirmation_number: 'CNF-ACTIVE',
          order_status: 'ACTIVE',
        }),
        createOrderCandidate({
          order_id: '20003',
          display_order_key: 'GRP-DELETED',
          group_code: 'GRP-DELETED',
          confirmation_number: 'CNF-DELETED',
          order_status: 'LOGIC_DELETED',
        }),
      ]),
    )
    vi.mocked(service.convertReservationManualReviewTask).mockResolvedValue(
      createManualReviewConversionResult({
        order_id: '20002',
        original_order_id: '20001',
        system_task_type: 'UPDATE_BOOKING',
        task_card_type: 'UPDATE_BOOKING',
      }),
    )

    const wrapper = await mountPanel()

    await wrapper.find('select[name="manual_target_task_type"]').setValue('UPDATE_BOOKING')
    await findButton(wrapper, '转换任务').trigger('click')
    await flushPromises()

    expect(service.convertReservationManualReviewTask).not.toHaveBeenCalled()
    expect(wrapper.text()).toContain('请选择目标订单')

    await wrapper.find('input[name="manual_target_order_keyword"]').setValue('GRP')
    await findButton(wrapper, '搜索订单').trigger('click')
    await flushPromises()

    expect(service.fetchReservationOrders).toHaveBeenCalledWith(
      expect.objectContaining({
        keyword: 'GRP',
        page_num: 1,
        page_size: 8,
      }),
    )
    expect(wrapper.text()).toContain('GRP-ACTIVE')
    expect(wrapper.text()).toContain('GRP-DELETED')
    expect(wrapper.find('input[value="20003"]').attributes('disabled')).toBeDefined()

    await wrapper.find('input[value="20002"]').setValue()
    await findButton(wrapper, '转换任务').trigger('click')
    await flushPromises()

    expect(service.convertReservationManualReviewTask).toHaveBeenCalledWith('10001', {
      target_task_type: 'UPDATE_BOOKING',
      target_order_id: '20002',
    })
  })

  it('hides task processing buttons and audit timeline without permissions', async () => {
    vi.mocked(service.fetchReservationTaskAudits).mockResolvedValue({
      task_id: '10001',
      items: [createAudit('audit-1', 'TASK_LOADED')],
    })
    vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
      createTaskDetail({
        availability: {
          blocked: false,
          read_only: false,
          editable: true,
          confirmable: true,
          executable: true,
          blocked_by_task_id: null,
          blocked_reason: null,
        },
        opera_operations: [
          {
            operation_id: 'op-1',
            task_id: '10001',
            operation_sequence: 1,
            operation_code: 'CREATE_RESERVATION',
            operation_name: 'Create reservation',
            operation_status: 'PENDING',
            attempt_count: 0,
            last_error_message: null,
            attempts: [],
          },
        ],
      }),
    )

    const wrapper = await mountPanel(['RESERVATION_TASK_READ'])

    expect(wrapper.text()).not.toContain('保存草稿')
    expect(wrapper.text()).not.toContain('确认任务')
    expect(wrapper.text()).not.toContain('审计时间线')
    expect(wrapper.find('button[title="执行"]').exists()).toBe(false)
    expect(wrapper.find('button[title="重试"]').exists()).toBe(false)
  })
})
