import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { fileURLToPath } from 'node:url'

import {
  buildApplicationPreviewFooterMessage,
  buildApplicationPreviewRows,
  buildApplicationPreviewSubmitText,
  buildApplicationTemplatePreview,
  applyApplicationPolicyEstimateResult,
  buildApplicationPolicyEstimateRequest,
  buildLocalApplicationPreview,
  buildLocalApplicationPreviewMessage,
  buildModelRefinedApplicationPreview,
  normalizeApplicationPreview,
  shouldUseLocalApplicationPreview
} from '../src/utils/expenseApplicationPreview.js'
import { renderMarkdown } from '../src/utils/markdown.js'
import {
  createMessage as createConversationMessage,
  hasMeaningfulSessionMessages
} from '../src/views/scripts/travelReimbursementConversationModel.js'

const submitComposerScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)),
  'utf8'
)
const createViewScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/TravelReimbursementCreateView.js', import.meta.url)),
  'utf8'
)
const createViewTemplate = readFileSync(
  fileURLToPath(new URL('../src/views/TravelReimbursementCreateView.vue', import.meta.url)),
  'utf8'
)
const messageItemTemplate = readFileSync(
  fileURLToPath(new URL('../src/components/travel/TravelReimbursementMessageItem.vue', import.meta.url)),
  'utf8'
)
const messageItemStyles = readFileSync(
  fileURLToPath(new URL('../src/assets/styles/components/travel-reimbursement-message-item.css', import.meta.url)),
  'utf8'
)
const conversationModelScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/travelReimbursementConversationModel.js', import.meta.url)),
  'utf8'
)
const previewEditorScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useApplicationPreviewEditor.js', import.meta.url)),
  'utf8'
)

test('application intent uses local preview instead of immediate orchestrator call', () => {
  const prompt = '申请 2026-05-20 至 2026-05-23 去上海支撑上海电力部署项目，出差3天，高铁，预计金额2358元'
  assert.equal(
    shouldUseLocalApplicationPreview(prompt, {
      sessionType: 'application',
      attachmentCount: 0,
      reviewAction: '',
      systemGenerated: false
    }),
    true
  )
  assert.equal(
    shouldUseLocalApplicationPreview('帮我查询申请状态', {
      sessionType: 'application',
      attachmentCount: 0,
      reviewAction: '',
      systemGenerated: false
    }),
    false
  )

  const preview = buildLocalApplicationPreview(prompt, { name: '李文静', departmentName: '财务部', grade: 'P5' })
  assert.equal(preview.fields.applicationType, '差旅费用申请')
  assert.equal(preview.fields.time, '2026-05-20 至 2026-05-23')
  assert.equal(preview.fields.location, '上海')
  assert.equal(preview.fields.days, '3天')
  assert.equal(preview.fields.transportMode, '火车')
  assert.equal(preview.fields.amount, '2358元')
  assert.equal(preview.fields.grade, 'P5')
  assert.equal(preview.readyToSubmit, true)
  assert.doesNotMatch(buildLocalApplicationPreviewMessage(preview), /#application-submit/)
  assert.match(buildApplicationPreviewFooterMessage(preview), /#application-submit/)
  assert.match(buildLocalApplicationPreviewMessage(preview), /点击对应行即可直接编辑/)
})

test('application preview renders ordered editable rows and submit text uses edited values', () => {
  const preview = buildLocalApplicationPreview('申请 2026-05-25 至 2026-05-28 去新疆，伊犁出差，服务美团业务部署，火车，预计费用1800元', {
    name: '李文静',
    grade: 'P5'
  })
  assert.equal(preview.fields.location, '新疆，伊犁')
  assert.equal(preview.fields.reason, '服务美团业务部署')
  const editedPreview = normalizeApplicationPreview({
    ...preview,
    fields: {
      ...preview.fields,
      reason: '客户现场项目支持',
      amount: '1900元'
    }
  })

  const rows = buildApplicationPreviewRows(editedPreview)
  assert.deepEqual(
    rows.map((row) => row.label),
    ['申请类型', '职级', '发生时间', '地点', '事由', '天数', '出行方式', '住宿上限/天', '补贴标准/天', '交通费用口径', '规则测算参考', '用户预估费用']
  )
  assert.equal(rows.find((row) => row.key === 'amount')?.value, '1900元')
  assert.equal(rows.find((row) => row.key === 'amount')?.highlight, true)
  assert.equal(rows.find((row) => row.key === 'grade')?.editable, false)
  assert.equal(rows.find((row) => row.key === 'lodgingDailyCap')?.editable, false)
  assert.match(buildApplicationPreviewSubmitText(editedPreview), /事由：客户现场项目支持/)
  assert.match(buildApplicationPreviewSubmitText(editedPreview), /用户预估费用：1900元/)
})

test('application preview cleans empty time labels and keeps only business reason', () => {
  const preview = buildLocalApplicationPreview('发生时间：，去九江出差3天，服务美团业务部署，预计费用1800元，火车', {
    name: '李文静',
    grade: 'P5'
  })

  assert.equal(preview.fields.location, '九江')
  assert.equal(preview.fields.days, '3天')
  assert.equal(preview.fields.reason, '服务美团业务部署')
  assert.equal(preview.fields.transportMode, '火车')
  assert.doesNotMatch(preview.fields.reason, /发生时间|去九江|出差3天/)
})

test('application preview can be refined by ontology model extraction', () => {
  const rawText = '发生时间：，去九江出差3天，服务美团业务部署，预计费用1800元，火车'
  const localPreview = buildLocalApplicationPreview(rawText, { name: '李文静', grade: 'P5' })
  const refinedPreview = buildModelRefinedApplicationPreview(
    localPreview,
    {
      parse_strategy: 'llm_primary',
      entities: [
        { type: 'expense_type', value: '差旅费', normalized_value: 'travel' },
        { type: 'location', value: '九江', normalized_value: '九江' },
        { type: 'reason', value: '服务美团业务部署', normalized_value: '服务美团业务部署' },
        { type: 'transport_mode', value: '火车', normalized_value: '火车' },
        { type: 'amount', value: '1800元', normalized_value: '1800' }
      ],
      time_range: {},
      missing_slots: []
    },
    rawText,
    { name: '李文静', grade: 'P5' }
  )

  assert.equal(refinedPreview.modelRefined, true)
  assert.equal(refinedPreview.parseStrategy, 'llm_primary')
  assert.equal(refinedPreview.modelReviewStatus, 'completed')
  assert.equal(refinedPreview.fields.applicationType, '差旅费用申请')
  assert.equal(refinedPreview.fields.time, '')
  assert.equal(refinedPreview.fields.reason, '服务美团业务部署')
  assert.equal(refinedPreview.fields.transportMode, '火车')
})

test('application preview keeps rule fallback distinct from model reviewed result', () => {
  const rawText = '申请 2026-05-20 至 2026-05-23 去上海支撑服务器部署，出差3天，火车，预计费用1800元'
  const localPreview = buildLocalApplicationPreview(rawText, { name: '李文静', grade: 'P5' })
  const fallbackPreview = buildModelRefinedApplicationPreview(
    localPreview,
    {
      parse_strategy: 'rule_fallback',
      entities: [
        { type: 'expense_type', value: '差旅费', normalized_value: 'travel' },
        { type: 'location', value: '上海', normalized_value: '上海' },
        { type: 'amount', value: '1800元', normalized_value: '1800' }
      ],
      time_range: {
        start: '2026-05-20',
        end: '2026-05-23'
      },
      missing_slots: []
    },
    rawText,
    { name: '李文静', grade: 'P5' }
  )
  const message = buildLocalApplicationPreviewMessage(fallbackPreview)
  const footer = buildApplicationPreviewFooterMessage(fallbackPreview)

  assert.equal(fallbackPreview.modelReviewStatus, 'fallback')
  assert.match(message, /规则兜底/)
  assert.match(footer, /请确认上述的信息是否填写正确/)
  assert.match(footer, /#application-submit/)
})

test('application preview with missing budget stays in chat and asks for补充信息', () => {
  const preview = buildLocalApplicationPreview('我想申请去北京出差，高铁，但是不知道预算', {
    name: '李文静',
    grade: 'P5'
  })
  assert.equal(preview.fields.amount, '待测算')
  assert.equal(preview.readyToSubmit, false)
  assert.match(buildLocalApplicationPreviewMessage(preview), /下方表格/)
  assert.doesNotMatch(buildLocalApplicationPreviewMessage(preview), /当前还需要补充/)
  assert.match(buildApplicationPreviewFooterMessage(preview), /当前还需要补充/)
  assert.doesNotMatch(buildLocalApplicationPreviewMessage(preview), /#application-submit/)
})

test('application quick start renders a template without model review', () => {
  const preview = buildApplicationTemplatePreview({
    name: '李文静',
    departmentName: '财务部',
    grade: 'P5'
  })
  const message = buildLocalApplicationPreviewMessage(preview)

  assert.equal(preview.modelReviewStatus, 'template')
  assert.equal(preview.fields.applicationType, '费用申请')
  assert.equal(preview.fields.applicant, '李文静')
  assert.equal(preview.fields.department, '财务部')
  assert.equal(preview.fields.grade, 'P5')
  assert.equal(buildApplicationPreviewRows(preview).find((row) => row.key === 'grade')?.editable, false)
  assert.match(message, /不调用大模型/)
  assert.match(message, /点击对应行直接填写/)
  assert.doesNotMatch(message, /#application-submit/)
  assert.match(buildApplicationPreviewFooterMessage(preview), /当前还需要补充/)
})

test('application quick start template counts as deletable session content', () => {
  const welcomeMessage = createConversationMessage('assistant', '欢迎语', [], {
    isWelcome: true,
    welcomeQuickActions: [{ label: '快速发起申请', action: 'start_guided_application' }]
  })
  const templateMessage = createConversationMessage('assistant', '申请模板', [], {
    applicationPreview: buildApplicationTemplatePreview({
      name: '测试员工',
      departmentName: '财务部',
      grade: 'P5'
    })
  })

  assert.equal(hasMeaningfulSessionMessages([welcomeMessage]), false)
  assert.equal(hasMeaningfulSessionMessages([welcomeMessage, templateMessage]), true)
})

test('application session shows intent flow, persists preview, and supports inline table edit', () => {
  assert.match(submitComposerScript, /shouldUseLocalApplicationPreview/)
  assert.match(submitComposerScript, /buildLocalApplicationPreviewMessage/)
  assert.match(submitComposerScript, /buildApplicationPreviewWithModelReview/)
  assert.match(submitComposerScript, /fetchOntologyParse/)
  assert.match(submitComposerScript, /calculateTravelReimbursement/)
  assert.match(submitComposerScript, /buildApplicationPolicyEstimateRequest/)
  assert.match(submitComposerScript, /模型复核中/)
  assert.match(submitComposerScript, /startFlowStep\('intent'/)
  assert.match(submitComposerScript, /startFlowStep\('application-review-preview'/)
  assert.match(submitComposerScript, /completeFlowStep\('intent'/)
  assert.doesNotMatch(submitComposerScript, /insightPanelCollapsed\.value = true/)
  assert.doesNotMatch(submitComposerScript, /void refineApplicationPreviewWithModel/)
  assert.match(submitComposerScript, /return null[\s\S]*const hasUnsavedReviewDraft/)
  assert.ok(
    submitComposerScript.indexOf('shouldUseLocalApplicationPreview') <
      submitComposerScript.indexOf('const payload = await runOrchestrator')
  )

  assert.match(createViewScript, /const isApplicationSession = computed/)
  assert.match(createViewScript, /insightPanelCollapsed,/)
  assert.doesNotMatch(createViewScript, /if \(isApplicationSession\.value\) \{\s*return false\s*\}/)
  assert.match(createViewScript, /activeFlowSteps\.value\.length > 0/)
  assert.match(createViewScript, /useApplicationPreviewEditor/)
  assert.match(createViewScript, /message-bubble-application-preview/)
  assert.match(createViewScript, /buildApplicationPreviewFooterMessage/)
  assert.match(createViewScript, /function buildApplicationPreviewFooterText\(message\)/)
  assert.match(createViewScript, /buildApplicationPreviewSubmitText/)
  assert.match(createViewScript, /user_input_text: applicationSubmitText/)
  assert.match(conversationModelScript, /applicationPreview: null/)
  assert.match(conversationModelScript, /applicationPreview: message\.applicationPreview \|\| null/)
  assert.match(conversationModelScript, /\|\| message\.applicationPreview/)
  assert.match(createViewScript, /hasMeaningfulSessionMessages\(messages\.value\)/)

  assert.match(messageItemTemplate, /class="application-preview-table"/)
  assert.match(messageItemTemplate, /class="application-preview-footer application-preview-footer-missing"/)
  assert.match(messageItemTemplate, /application-preview-missing-chip/)
  assert.match(messageItemTemplate, /当前还需要补充：/)
  assert.match(messageItemTemplate, /补齐后我再帮您提交申请。/)
  assert.match(messageItemTemplate, /class="application-preview-footer message-answer-content message-answer-markdown"/)
  assert.match(messageItemTemplate, /v-html="ui\.renderMarkdown\(ui\.buildApplicationPreviewFooterText\(message\)\)"/)
  assert.match(createViewTemplate, /'has-insight': hasInsightPanelContent && showInsightPanel/)
  assert.match(messageItemTemplate, /v-model="ui\.applicationPreviewEditor\.draftValue"/)
  assert.match(messageItemTemplate, /application-preview-select/)
  assert.match(messageItemTemplate, /resolveApplicationPreviewEditorOptions/)
  assert.match(messageItemTemplate, /row\.editable && !ui\.isApplicationPreviewEditing\(message, row\.key\).*ui\.openApplicationPreviewEditor\(message, row\.key, row\.value\)"/)
  assert.match(messageItemTemplate, /@keydown\.enter\.prevent="row\.editable && !ui\.isApplicationPreviewEditing\(message, row\.key\).*ui\.openApplicationPreviewEditor\(message, row\.key, row\.value\)"/)
  assert.match(messageItemTemplate, /@keydown\.stop="ui\.handleApplicationPreviewEditorKeydown\(\$event, message\)"/)
  assert.match(messageItemTemplate, /mdi mdi-pencil-outline/)
  assert.match(messageItemTemplate, /@click\.stop="ui\.openApplicationPreviewEditor\(message, row\.key, row\.value\)"/)
  assert.match(messageItemTemplate, /openApplicationPreviewEditor/)
  assert.match(messageItemTemplate, /commitApplicationPreviewEditor/)
  assert.match(createViewScript, /resolveApplicationPreviewMissingFields/)

  assert.match(previewEditorScript, /normalizeApplicationPreview/)
  assert.match(previewEditorScript, /APPLICATION_TRANSPORT_MODE_OPTIONS/)
  assert.match(previewEditorScript, /buildLocalApplicationPreviewMessage/)
  assert.match(previewEditorScript, /targetRow\.editable === false/)
  assert.match(previewEditorScript, /\[editor\.fieldKey\]: nextValue/)

  assert.match(messageItemStyles, /\.application-preview-row\.missing \{[\s\S]*--theme-primary-rgb/)
  assert.match(messageItemStyles, /\.application-preview-table \{[\s\S]*border: 1px solid #d7e4f2;[\s\S]*background: #ffffff;/)
  assert.match(messageItemStyles, /\.application-preview-row \{[\s\S]*grid-template-columns: 108px minmax\(0, 1fr\);/)
  assert.match(messageItemStyles, /\.application-preview-text \{[\s\S]*overflow-wrap: anywhere;/)
  assert.match(messageItemStyles, /\.application-preview-select \{[\s\S]*width: 100%;/)
  assert.match(messageItemStyles, /\.application-preview-footer \{[\s\S]*margin-top: 48px;/)
  assert.match(messageItemStyles, /\.message-answer-markdown :deep\(\.markdown-action-link\) \{[\s\S]*text-decoration: underline;/)
  assert.match(messageItemStyles, /\.application-preview-footer-missing \{[\s\S]*margin-top: 48px;[\s\S]*background: transparent;/)
  assert.match(messageItemStyles, /\.application-preview-missing-chip \{[\s\S]*background: rgba\(var\(--theme-primary-rgb/)
})

test('assistant markdown tables render with component-scoped table styling', () => {
  const rendered = renderMarkdown([
    '| 项目 | 标准口径 | 天数 | 小计 |',
    '| --- | --- | ---: | ---: |',
    '| 住宿费 | 武汉 / P5 标准：330.00 元/天 | 1 | 330.00 元 |',
    '| 出差补贴 | 其他地区：伙食 55.00 元 + 基本 35.00 元 | 1 | 90.00 元 |'
  ].join('\n'))

  assert.match(rendered, /<div class="markdown-table-wrap">/)
  assert.match(rendered, /<table>/)
  assert.match(rendered, /<th/)
  assert.match(rendered, /<td/)
  assert.match(messageItemStyles, /\.message-answer-markdown :deep\(\.markdown-table-wrap\) \{[\s\S]*overflow-x: auto;[\s\S]*border: 1px solid #dbe4ee;/)
  assert.match(messageItemStyles, /\.message-answer-markdown :deep\(table\) \{[\s\S]*min-width: 460px;[\s\S]*border-collapse: separate;/)
  assert.match(messageItemStyles, /\.message-answer-markdown :deep\(th\),[\s\S]*\.message-answer-markdown :deep\(td\) \{[\s\S]*padding: 8px 10px;/)
})

test('application preview merges rule center travel estimate into highlighted rows', () => {
  const preview = buildLocalApplicationPreview('申请 2026-05-25 至 2026-05-28 去上海出差3天，服务项目部署，火车，预计费用1800元', {
    name: '李文静',
    grade: 'P5'
  })
  const request = buildApplicationPolicyEstimateRequest(preview, { grade: 'P5' })
  assert.equal(request.canCalculate, true)
  assert.deepEqual(request.payload, { days: 3, location: '上海', grade: 'P5' })

  const estimatedPreview = applyApplicationPolicyEstimateResult(preview, {
    days: 3,
    location: '上海',
    matched_city: '上海',
    grade: 'P5',
    hotel_rate: 600,
    hotel_amount: 1800,
    total_allowance_rate: 120,
    allowance_amount: 360,
    total_amount: 2160,
    rule_name: '公司差旅费报销规则',
    rule_version: '2026版'
  }, { grade: 'P5' })

  assert.equal(estimatedPreview.fields.lodgingDailyCap, '600元/天')
  assert.equal(estimatedPreview.fields.subsidyDailyCap, '120元/天')
  assert.match(estimatedPreview.fields.transportPolicy, /实报实销/)
  assert.match(estimatedPreview.fields.policyEstimate, /2,160元/)
  assert.equal(buildApplicationPreviewRows(estimatedPreview).find((row) => row.key === 'policyEstimate')?.highlight, true)
})
