import { flushPromises, mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import PrimeVue from 'primevue/config'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { createI18n } from 'vue-i18n'

import enUS from '@/i18n/locales/en-US'
import thTH from '@/i18n/locales/th-TH'
import zhCN from '@/i18n/locales/zh-CN'
import { ApiError } from '@/services/httpClient'
import { useAuthStore } from '@/stores/authStore'
import type { AuthHotelResult } from '@/types/auth'
import type { ManualInvoiceGenerationResult } from '@/types/manualInvoice'
import ReservationManualInvoiceView from '@/views/reservation/ReservationManualInvoiceView.vue'

vi.mock('@/services/reservationService', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@/services/reservationService')>()
  return {
    ...actual,
    generateManualReservationInvoice: vi.fn(),
  }
})

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

function createResult(): ManualInvoiceGenerationResult {
  return {
    invoice_generation_id: '91001',
    generation_status: 'GENERATED',
    source_type: 'MANUAL',
    hotel_id: 'HOTEL-TEST',
    order_id: null,
    task_id: null,
    template_code: 'PROFORMA_INVOICE_V1',
    pdf_url: 'https://oss.example/invoices/manual-91001.pdf',
    pdf_object_key: 'reservation/invoices/manual-91001.pdf',
    generated_excel_object_key: 'reservation/invoices/manual-91001.xlsx',
    totals: {
      subtotal: 14018.69,
      vat: 981.31,
      total: 15000,
      currency: 'THB',
    },
    created_at: '2026-07-17T08:00:00Z',
  }
}

const nativeCreateObjectURL = URL.createObjectURL
const nativeRevokeObjectURL = URL.revokeObjectURL

type ManualInvoiceTestLocale = 'zh-CN' | 'en-US' | 'th-TH'

function mountView(options: { timeZone?: string; extraHotels?: AuthHotelResult[]; locale?: ManualInvoiceTestLocale } = {}) {
  const i18n = createI18n({
    legacy: false,
    locale: options.locale ?? 'zh-CN',
    fallbackLocale: 'zh-CN',
    messages: {
      'zh-CN': zhCN,
      'en-US': enUS,
      'th-TH': thTH,
    },
  })
  const pinia = createPinia()
  setActivePinia(pinia)
  useAuthStore().applyLoginResult({
    access_token: 'session-token',
    token_type: 'Bearer',
    expires_at: '2026-07-18T00: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: options.timeZone ?? 'Asia/Bangkok',
        default_hotel: true,
      },
      ...(options.extraHotels ?? []),
    ],
    permissions: ['RESERVATION_INVOICE_GENERATE'],
    menus: [],
  })

  const wrapper = mount(ReservationManualInvoiceView, {
    global: {
      plugins: [i18n, pinia, PrimeVue],
    },
  })
  return Object.assign(wrapper, {
    async setLocale(locale: ManualInvoiceTestLocale) {
      i18n.global.locale.value = locale
      await nextTick()
    },
  })
}

async function fillMinimumInvoiceForm(wrapper: ReturnType<typeof mountView>) {
  await wrapper.find('[data-testid="document-invoice-date"]').setValue('2026-07-17')
  await wrapper.find('[data-testid="document-booking-date"]').setValue('2026-07-17')
  await wrapper.find('[data-testid="document-due-date"]').setValue('2026-07-24')
  await wrapper.find('[data-testid="booking-group-name"]').setValue('LLT260509FA213')
  await wrapper.find('[data-testid="booking-arrival-date"]').setValue('2026-07-20')
  await wrapper.find('[data-testid="booking-departure-date"]').setValue('2026-07-22')
  await wrapper.find('[data-testid="booking-room-rate-note"]').setValue('Inclusive breakfast')
  await wrapper.find('[data-testid="booking-extra-bed-rate"]').setValue('900')
  await wrapper.find('[data-testid="charge-description-0"]').setValue('2N TWN +1 DBL')
  await wrapper.find('[data-testid="charge-room-type-0"]').setValue('TWN/DBL')
  await wrapper.find('[data-testid="charge-quantity-0"]').setValue('3')
  await wrapper.find('[data-testid="charge-rate-0"]').setValue('2500')
  await wrapper.find('[data-testid="charge-nights-0"]').setValue('2')
}

describe('ReservationManualInvoiceView', () => {
  beforeEach(() => {
    vi.mocked(service.generateManualReservationInvoice).mockReset()
    sessionStorage.clear()
    Object.defineProperty(window, 'matchMedia', {
      configurable: true,
      writable: true,
      value: vi.fn().mockImplementation((query: string) => ({
        matches: false,
        media: query,
        onchange: null,
        addListener: vi.fn(),
        removeListener: vi.fn(),
        addEventListener: vi.fn(),
        removeEventListener: vi.fn(),
        dispatchEvent: vi.fn(),
      })),
    })
    Object.defineProperty(URL, 'createObjectURL', {
      configurable: true,
      value: vi.fn(() => 'blob:manual-invoice-pdf'),
    })
    Object.defineProperty(URL, 'revokeObjectURL', {
      configurable: true,
      value: vi.fn(),
    })
    vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => undefined)
  })

  afterEach(() => {
    vi.useRealTimers()
    vi.unstubAllGlobals()
    vi.restoreAllMocks()
    document.body.innerHTML = ''
    if (nativeCreateObjectURL) {
      Object.defineProperty(URL, 'createObjectURL', {
        configurable: true,
        value: nativeCreateObjectURL,
      })
    } else {
      Reflect.deleteProperty(URL, 'createObjectURL')
    }
    if (nativeRevokeObjectURL) {
      Object.defineProperty(URL, 'revokeObjectURL', {
        configurable: true,
        value: nativeRevokeObjectURL,
      })
    } else {
      Reflect.deleteProperty(URL, 'revokeObjectURL')
    }
  })

  it('defaults document dates in the selected hotel timezone', () => {
    vi.useFakeTimers()
    vi.setSystemTime(new Date('2026-07-17T12:30:00Z'))

    const wrapper = mountView({ timeZone: 'Pacific/Kiritimati' })

    expect((wrapper.find('[data-testid="document-invoice-date"]').element as HTMLInputElement).value).toBe(
      '2026-07-18',
    )
    expect((wrapper.find('[data-testid="document-booking-date"]').element as HTMLInputElement).value).toBe(
      '2026-07-18',
    )
    expect((wrapper.find('[data-testid="document-due-date"]').element as HTMLInputElement).value).toBe('2026-07-25')
  })

  it('refreshes untouched document dates when switching hotel timezone', async () => {
    vi.useFakeTimers()
    vi.setSystemTime(new Date('2026-07-17T12:30:00Z'))
    const wrapper = mountView({
      timeZone: 'Asia/Bangkok',
      extraHotels: [
        {
          hotel_id: 'HOTEL-KIRITIMATI',
          hotel_name: '换日线酒店',
          time_zone: 'Pacific/Kiritimati',
          default_hotel: false,
        },
      ],
    })
    const authStore = useAuthStore()

    expect((wrapper.find('[data-testid="document-invoice-date"]').element as HTMLInputElement).value).toBe(
      '2026-07-17',
    )

    authStore.setSelectedHotelId('HOTEL-KIRITIMATI')
    await nextTick()

    expect((wrapper.find('[data-testid="document-invoice-date"]').element as HTMLInputElement).value).toBe(
      '2026-07-18',
    )
    expect((wrapper.find('[data-testid="document-due-date"]').element as HTMLInputElement).value).toBe('2026-07-25')
  })

  it('does not overwrite manually edited document dates when switching hotel timezone', async () => {
    vi.useFakeTimers()
    vi.setSystemTime(new Date('2026-07-17T12:30:00Z'))
    const wrapper = mountView({
      timeZone: 'Asia/Bangkok',
      extraHotels: [
        {
          hotel_id: 'HOTEL-KIRITIMATI',
          hotel_name: '换日线酒店',
          time_zone: 'Pacific/Kiritimati',
          default_hotel: false,
        },
      ],
    })
    const authStore = useAuthStore()

    await wrapper.find('[data-testid="document-invoice-date"]').setValue('2026-08-01')
    authStore.setSelectedHotelId('HOTEL-KIRITIMATI')
    await nextTick()

    expect((wrapper.find('[data-testid="document-invoice-date"]').element as HTMLInputElement).value).toBe(
      '2026-08-01',
    )
  })

  it('renders the invoice.html style page sections and sticky action bar', () => {
    const wrapper = mountView()

    expect(wrapper.find('[data-testid="manual-invoice-prototype-page"]').exists()).toBe(true)
    expect(wrapper.text()).toContain('创建 Invoice')
    expect(wrapper.text()).not.toContain('草稿')
    expect(wrapper.text()).toContain('01')
    expect(wrapper.text()).toContain('Basic Information')
    expect(wrapper.text()).toContain('02')
    expect(wrapper.text()).toContain('Please refer to the following reservation')
    expect(wrapper.text()).not.toContain('Group Name 与 Group Code 在本流程中为同一字段。')
    expect(wrapper.text()).toContain('03')
    expect(wrapper.text()).toContain('Description&Amount')
    expect(wrapper.find('[data-testid="manual-invoice-action-bar"]').exists()).toBe(true)
  })

  it('localizes the highlighted invoice title and recipient placeholders', () => {
    const wrapper = mountView({ locale: 'en-US' })
    const placeholders = wrapper.findAll('input, textarea').map((field) => field.attributes('placeholder'))

    expect(wrapper.text()).toContain('Create Invoice')
    expect(wrapper.text()).not.toContain('创建 Invoice')
    expect(placeholders).toContain('Enter company')
    expect(placeholders).toContain('Enter attention')
    expect(placeholders).toContain('Enter full billing address')
    expect(placeholders).not.toContain('输入 Company')
    expect(placeholders).not.toContain('输入 Attention')
    expect(placeholders).not.toContain('输入客户完整账单地址')
  })

  it('localizes the invoice action controls and room rate note placeholder', async () => {
    const wrapper = mountView({ locale: 'en-US' })

    expect((wrapper.find('[data-testid="booking-room-rate-note"]').element as HTMLInputElement).placeholder).toBe(
      'e.g. including BF',
    )
    expect(wrapper.find('.control-caption').exists()).toBe(false)
    expect(wrapper.find('[data-testid="manual-invoice-add-charge"]').text()).toContain('Add row')
    expect(wrapper.find('[data-testid="manual-invoice-submit"]').text()).toContain('Confirm and generate PDF')
    expect(wrapper.text()).toContain('Row 1')
    expect(wrapper.text()).not.toContain('第 1 行')

    await wrapper.find('[data-testid="manual-invoice-add-charge"]').trigger('click')

    expect(wrapper.findAll('.remove-button')[1]?.text()).toContain('Remove')
    expect(wrapper.findAll('.remove-button')[1]?.attributes('aria-label')).toBe('Remove row 2')
    expect(wrapper.text()).toContain('Row 2')
    expect(wrapper.text()).not.toContain('新增一行')
    expect(wrapper.text()).not.toContain('确认并生成 PDF')
    expect(wrapper.text()).not.toContain('例如 includingBF')
    expect(wrapper.text()).not.toContain('金额')
    expect(wrapper.text()).not.toContain('说明')
  })

  it('uses localized DatePicker controls for date fields', async () => {
    const wrapper = mountView({ locale: 'th-TH' })
    const arrivalDateInput = wrapper.find('[data-testid="booking-arrival-date"]')

    expect(arrivalDateInput.attributes('type')).toBe('text')
    expect(arrivalDateInput.attributes('placeholder')).toBe(thTH.manualInvoice.datePlaceholder)
    expect(arrivalDateInput.attributes('lang')).toBe('th-TH')
    expect(wrapper.find('[data-testid="document-booking-date"]').attributes('type')).toBe('text')
    expect(wrapper.find('[data-testid="booking-departure-date"]').attributes('type')).toBe('text')
    expect(wrapper.find('[data-testid="document-due-date"]').attributes('type')).toBe('text')

    await arrivalDateInput.setValue('2026-07-17')
    await arrivalDateInput.trigger('click')
    await flushPromises()

    const arrivalDatePanel = document.body.querySelector('[data-testid="booking-arrival-date-panel"]')

    expect(wrapper.find('[data-testid="booking-arrival-date-panel"]').exists()).toBe(false)
    expect(arrivalDatePanel?.textContent).toContain('กรกฎาคม')
    expect(arrivalDatePanel?.textContent).not.toContain('2026年07月')
  })

  it('marks required fields and highlights empty inputs after submit', async () => {
    const wrapper = mountView()

    expect(wrapper.find('label[for="invoice-company"].field-label--required').exists()).toBe(true)
    expect(wrapper.find('[data-testid="recipient-company-input"]').classes()).not.toContain('is-invalid')

    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(service.generateManualReservationInvoice).not.toHaveBeenCalled()
    expect(wrapper.find('[data-testid="recipient-company-input"]').classes()).toContain('is-invalid')
    expect(wrapper.find('[data-testid="recipient-company-input"]').attributes('aria-invalid')).toBe('true')
    expect(wrapper.find('[data-testid="manual-invoice-field-error-company"]').text()).toBe(
      zhCN.manualInvoice.fieldErrors.required,
    )
    expect(wrapper.find('[data-testid="booking-group-name"]').classes()).toContain('is-invalid')
    expect(wrapper.find('[data-testid="primary-room-count"]').classes()).toContain('is-invalid')
    expect(wrapper.find('[data-testid="charge-description-0"]').classes()).toContain('is-invalid')

    await wrapper.find('[data-testid="recipient-company-input"]').setValue('Manual Company')

    expect(wrapper.find('[data-testid="recipient-company-input"]').classes()).not.toContain('is-invalid')
    expect(wrapper.find('[data-testid="manual-invoice-field-error-company"]').exists()).toBe(false)
  })

  it('shows field-level validation messages for invalid email, dates, and numbers', async () => {
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="recipient-email"]').setValue('bad-email')
    await wrapper.find('[data-testid="booking-departure-date"]').setValue('2026-07-19')
    await wrapper.find('[data-testid="booking-extra-bed-rate"]').setValue('-1')
    await wrapper.find('[data-testid="charge-rate-0"]').setValue('0')
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(service.generateManualReservationInvoice).not.toHaveBeenCalled()
    expect(wrapper.find('[data-testid="recipient-email"]').classes()).toContain('is-invalid')
    expect(wrapper.find('[data-testid="manual-invoice-field-error-email"]').text()).toBe(
      zhCN.manualInvoice.fieldErrors.invalidEmail,
    )
    expect(wrapper.find('[data-testid="booking-departure-date"]').classes()).toContain('is-invalid')
    expect(wrapper.find('[data-testid="manual-invoice-field-error-departure-date"]').text()).toBe(
      zhCN.manualInvoice.fieldErrors.dateOrder,
    )
    expect(wrapper.find('[data-testid="booking-extra-bed-rate"]').classes()).toContain('is-invalid')
    expect(wrapper.find('[data-testid="manual-invoice-field-error-extra-bed-rate"]').text()).toBe(
      zhCN.manualInvoice.fieldErrors.nonNegativeNumber,
    )
    expect(wrapper.find('[data-testid="charge-rate-0"]').classes()).toContain('is-invalid')
    expect(wrapper.find('[data-testid="manual-invoice-field-error-charge-0-rate"]').text()).toBe(
      zhCN.manualInvoice.fieldErrors.positiveNumber,
    )
  })

  it('does not render final-format date preview blocks', () => {
    const wrapper = mountView()

    expect(wrapper.find('.date-preview').exists()).toBe(false)
    expect(wrapper.text()).not.toContain('最终格式')
  })

  it('keeps primary reservation controls bound to the first charge line', async () => {
    const wrapper = mountView()

    await wrapper.find('[data-testid="primary-room-rate"]').setValue('3200')
    await wrapper.find('[data-testid="primary-room-count"]').setValue('4')
    await wrapper.find('[data-testid="primary-night-count"]').setValue('3')

    expect((wrapper.find('[data-testid="charge-rate-0"]').element as HTMLInputElement).value).toBe('3200')
    expect((wrapper.find('[data-testid="charge-quantity-0"]').element as HTMLInputElement).value).toBe('4')
    expect((wrapper.find('[data-testid="charge-nights-0"]').element as HTMLInputElement).value).toBe('3')
    expect(wrapper.find('[data-testid="charge-amount-0"]').text()).toContain('38,400.00')
  })

  it('submits comma-formatted numeric inputs as normalized numbers', async () => {
    vi.mocked(service.generateManualReservationInvoice).mockResolvedValue(createResult())
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="booking-extra-bed-rate"]').setValue('1,200')
    await wrapper.find('[data-testid="charge-rate-0"]').setValue('3,000')
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(service.generateManualReservationInvoice).toHaveBeenCalledWith(
      expect.objectContaining({
        invoice_payload: expect.objectContaining({
          booking: expect.objectContaining({
            extra_bed_rate: 1200,
          }),
          charges: [
            expect.objectContaining({
              rate: 3000,
            }),
          ],
        }),
      }),
    )
  })

  it('clears directory contact text when switching company back to manual input', async () => {
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('HANATOUR')
    await wrapper.find('[data-testid="recipient-contact-id"]').setValue('HANATOUR_BOLAM_JO')
    expect((wrapper.find('[data-testid="recipient-email"]').element as HTMLInputElement).value).toBe(
      'melissa0609@hanatour.com',
    )

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('MANUAL')

    expect((wrapper.find('[data-testid="recipient-contact-id"]').element as HTMLSelectElement).value).toBe('MANUAL')
    expect((wrapper.find('[data-testid="recipient-company-input"]').element as HTMLInputElement).value).toBe('')
    expect((wrapper.find('[data-testid="recipient-attention-input"]').element as HTMLInputElement).value).toBe('')
    expect((wrapper.find('[data-testid="recipient-email"]').element as HTMLInputElement).value).toBe('')
  })

  it('keeps invoice date out of the keyboard focus order while preserving the payload field', () => {
    const wrapper = mountView()
    const invoiceDate = wrapper.find('[data-testid="document-invoice-date"]')

    expect(invoiceDate.attributes('type')).toBe('hidden')
    expect(invoiceDate.attributes('required')).toBeUndefined()
  })

  it('links company and attention seed data while allowing manual overrides before submit', async () => {
    vi.mocked(service.generateManualReservationInvoice).mockResolvedValue(createResult())
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('HANATOUR')
    await wrapper.find('[data-testid="recipient-contact-id"]').setValue('HANATOUR_BOLAM_JO')
    expect((wrapper.find('[data-testid="recipient-telephone"]').element as HTMLInputElement).value).toBe(
      '82 010 4182 4615',
    )

    await wrapper.find('[data-testid="recipient-email"]').setValue('custom@example.test')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(service.generateManualReservationInvoice).toHaveBeenCalledWith(
      expect.objectContaining({
        hotel_id: 'HOTEL-TEST',
        source_type: 'MANUAL',
        task_id: null,
        order_id: null,
        template_code: 'PROFORMA_INVOICE_V1',
        invoice_payload: expect.objectContaining({
          recipient: expect.objectContaining({
            company_code: 'HANATOUR',
            contact_id: 'HANATOUR_BOLAM_JO',
            attention: 'Bolam Jo',
            email: 'custom@example.test',
          }),
          charges: [
            expect.objectContaining({
              description: '2N TWN +1 DBL',
              quantity: 3,
              rate: 2500,
              nights: 2,
            }),
          ],
        }),
      }),
    )
    expect(wrapper.text()).toContain('91001')
    expect(wrapper.find('a[href="https://oss.example/invoices/manual-91001.pdf"]').exists()).toBe(true)
  })

  it('downloads the generated PDF through a browser blob URL', async () => {
    vi.mocked(service.generateManualReservationInvoice).mockResolvedValue(createResult())
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      blob: () => Promise.resolve(new Blob(['pdf'], { type: 'application/pdf' })),
    })
    vi.stubGlobal('fetch', fetchMock)
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()
    await wrapper.find('[data-testid="manual-invoice-download-pdf"]').trigger('click')
    await flushPromises()

    expect(fetchMock).toHaveBeenCalledWith('https://oss.example/invoices/manual-91001.pdf', {
      credentials: 'omit',
    })
    expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob))
    await new Promise((resolve) => {
      setTimeout(resolve, 0)
    })
    expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:manual-invoice-pdf')
  })

  it('localizes the PDF download fallback message after a locale switch', async () => {
    vi.mocked(service.generateManualReservationInvoice).mockResolvedValue(createResult())
    const fetchMock = vi.fn().mockRejectedValue(new Error('CORS blocked'))
    const openSpy = vi.spyOn(window, 'open').mockReturnValue(null)
    vi.stubGlobal('fetch', fetchMock)
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()
    await wrapper.find('[data-testid="manual-invoice-download-pdf"]').trigger('click')
    await flushPromises()

    expect(openSpy).toHaveBeenCalledWith('https://oss.example/invoices/manual-91001.pdf', '_blank', 'noopener,noreferrer')
    expect(wrapper.find('[data-testid="manual-invoice-download-message"]').text()).toBe(
      zhCN.manualInvoice.downloadFallback,
    )

    await wrapper.setLocale('en-US')

    expect(wrapper.find('[data-testid="manual-invoice-download-message"]').text()).toBe(
      enUS.manualInvoice.downloadFallback,
    )
  })

  it('submits complete numeric input values instead of parseFloat prefixes', async () => {
    vi.mocked(service.generateManualReservationInvoice).mockResolvedValue(createResult())
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="charge-rate-0"]').setValue('1e3')
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(service.generateManualReservationInvoice).toHaveBeenCalledWith(
      expect.objectContaining({
        invoice_payload: expect.objectContaining({
          charges: [
            expect.objectContaining({
              rate: 1000,
            }),
          ],
        }),
      }),
    )
  })

  it('limits manual charge lines to ten rows', async () => {
    const wrapper = mountView()
    const addButton = () => wrapper.find('[data-testid="manual-invoice-add-charge"]')

    for (let index = 1; index < 10; index += 1) {
      await addButton().trigger('click')
    }

    expect(wrapper.findAll('[data-testid^="charge-row-"]')).toHaveLength(10)
    expect(addButton().attributes('disabled')).toBeDefined()
  })

  it('shows a friendly backend error for document conversion timeout', async () => {
    vi.mocked(service.generateManualReservationInvoice).mockRejectedValue(
      new ApiError('Request failed with status 500', 500, {
        error_code: 'DOCUMENT_CONVERSION_TIMEOUT',
        message: 'Document conversion timeout.',
      }),
    )
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(wrapper.text()).toContain('PDF 生成超时')
  })

  it('keeps backend raw details in a folded technical detail block', async () => {
    vi.mocked(service.generateManualReservationInvoice).mockRejectedValue(
      new ApiError('Request failed with status 422', 422, {
        error_code: 'RESERVATION_INVOICE_VALIDATION_FAILED',
        message: 'invoice_payload.recipient.email is invalid',
        details: ['invoice_payload.recipient.email: must be a valid email'],
      }),
    )
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(wrapper.find('[data-testid="manual-invoice-user-messages"]').text()).toContain('Invoice 字段校验失败')
    expect(wrapper.find('[data-testid="manual-invoice-user-messages"]').text()).not.toContain('invoice_payload')
    expect(wrapper.find('[data-testid="manual-invoice-technical-details"]').text()).toContain(
      'invoice_payload.recipient.email is invalid',
    )
    expect(wrapper.find('[data-testid="manual-invoice-technical-details"]').text()).toContain(
      'invoice_payload.recipient.email: must be a valid email',
    )
  })

  it('blocks negative extra bed rate before calling the backend', async () => {
    const wrapper = mountView()

    await wrapper.find('[data-testid="recipient-company-code"]').setValue('QBD')
    await fillMinimumInvoiceForm(wrapper)
    await wrapper.find('[data-testid="booking-extra-bed-rate"]').setValue('-1')
    await wrapper.find('[data-testid="manual-invoice-submit"]').trigger('click')
    await flushPromises()

    expect(service.generateManualReservationInvoice).not.toHaveBeenCalled()
    expect(wrapper.text()).toContain('加床价格不能小于 0')
  })
})
