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

import { buildExpenseSceneSelectionActions } from '../src/utils/expenseAssistantActions.js'
import { buildExpenseSceneSelectionMessage } from '../src/views/scripts/travelReimbursementConversationModel.js'
import { normalizeInlineApplicationStatusLabel } from '../src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js'

function readSource(path) {
  return readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
}

const aiModeRuntimeDir = fileURLToPath(new URL('../src/composables/workbenchAiMode/', import.meta.url))
const applicationPreviewFlow = readSource('../src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js')
const applicationPreviewModel = readSource('../src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js')
const aiMode = [
  readSource('../src/components/business/PersonalWorkbenchAiMode.vue'),
  readSource('../src/components/business/PersonalWorkbenchAiMode.template.html'),
  readSource('../src/utils/aiDocumentQueryModel.js'),
  ...readdirSync(aiModeRuntimeDir)
    .filter((file) => file.endsWith('.js'))
    .sort()
    .map((file) => readSource(`../src/composables/workbenchAiMode/${file}`))
].join('\n')

test('expense scene selection message asks for type first and mentions application gate', () => {
  const text = buildExpenseSceneSelectionMessage('帮我发起一笔报销，并检查需要准备哪些票据材料。')

  assert.match(text, /先选|选择.*报销类型|报销场景/)
  assert.match(text, /差旅|招待|申请单|关联申请单/)
})

test('expense scene actions mark travel and meal as requiring application', () => {
  const actions = buildExpenseSceneSelectionActions('帮我发起一笔报销，并检查需要准备哪些票据材料。')
  const travel = actions.find((action) => action.payload.expense_type === 'travel')
  const meal = actions.find((action) => action.payload.expense_type === 'meal')
  const transport = actions.find((action) => action.payload.expense_type === 'transport')

  assert.equal(travel.payload.requires_application_before_reimbursement, true)
  assert.equal(travel.payload.next_session_type, 'application')
  assert.equal(meal.payload.requires_application_before_reimbursement, true)
  assert.equal(meal.payload.next_session_type, 'application')
  assert.equal(transport.payload.requires_application_before_reimbursement, false)
  assert.equal(transport.payload.next_session_type, 'expense')
})

test('AI mode quick reimbursement card enters the reimbursement association gate before steward plan', () => {
  assert.match(
    aiMode,
    /function runAiModeAction\(item\) {[\s\S]{0,300}expenseFlow\.startAiReimbursementAssociationGate\(item\.prompt, item\.label\)/
  )
})

test('AI mode expense scene selection stays in the inline conversation without opening the create view', () => {
  assert.match(aiMode, /actionType === 'select_expense_type'/)
  assert.doesNotMatch(aiMode, /emit\('open-assistant'/)
})

test('AI mode offers an inline application shortcut when no candidate application exists', () => {
  assert.match(aiMode, /!candidates\.length/)
  assert.match(aiMode, /ai_application_start_inline/)
  assert.match(aiMode, /buildRequiredApplicationMissingText/)
  assert.match(aiMode, /function startAiApplicationPreview/)
  assert.match(aiMode, /buildLocalApplicationPreview/)
  assert.match(aiMode, /buildLocalApplicationPreviewMessage/)
  assert.match(aiMode, /refreshApplicationPreviewEstimate/)
  assert.match(aiMode, /applicationPreview:\s*preview/)
  assert.match(aiMode, /suggestedActions:\s*buildInlineApplicationPreviewSuggestedActions\(registeredPreview\)/)
  assert.doesNotMatch(aiMode, /function startAiApplicationDraft/)
  assert.doesNotMatch(aiMode, /buildAiApplicationStepPrompt/)
})

test('AI mode steward reimbursement action enters the association gate while standalone flow keeps scene selection', () => {
  assert.match(aiMode, /buildExpenseSceneSelectionMessage/)
  assert.match(aiMode, /buildExpenseSceneSelectionActions/)
  assert.match(aiMode, /SESSION_TYPE_EXPENSE/)
  assert.match(aiMode, /function pushInlineExpenseSceneSelectionPrompt/)
  assert.match(aiMode, /action\?\.payload\?\.session_type[\s\S]*SESSION_TYPE_EXPENSE/)
  assert.match(aiMode, /expenseFlow\.startAiReimbursementAssociationGate\(carryText, action\.label\)/)
  assert.match(
    aiMode,
    /SESSION_TYPE_EXPENSE && carryText === '我要报销'[\s\S]{0,160}expenseFlow\.startAiReimbursementAssociationGate\(carryText, action\.label\)[\s\S]{0,40}return/
  )
  assert.match(aiMode, /SKIP_REQUIRED_APPLICATION_LINK_ACTION[\s\S]{0,180}pushInlineExpenseSceneSelectionPrompt/)
})

test('AI mode attaches required application lookup result before steward planning', () => {
  assert.match(aiMode, /async function attachAiRequiredApplicationGate\(planRequest, prompt\)/)
  assert.match(aiMode, /fetchExpenseClaims\(REIMBURSEMENT_LIST_PREVIEW_PARAMS\)/)
  assert.match(aiMode, /filterRequiredApplicationCandidates\(claims, 'travel', currentUser\.value \|\| \{\}\)/)
  assert.match(aiMode, /required_application_gate/)
  assert.match(aiMode, /await attachAiRequiredApplicationGate\(planRequest, prompt\)/)
})

test('AI mode handles document query prompts locally before steward planning', () => {
  assert.match(aiMode, /resolveAiDocumentQueryIntent\(prompt/)
  assert.match(aiMode, /async function handleAiDocumentQueryIntent/)
  assert.match(aiMode, /buildAiDocumentQueryConditionSummary/)
  assert.match(aiMode, /filterAiDocumentQueryRecords\(payload, intent\)/)
  assert.match(aiMode, /mergeAiDocumentQueryPayloads/)
  assert.match(aiMode, /fetchApprovalExpenseClaims/)
  assert.match(aiMode, /Promise\.all\(\[/)
  assert.match(aiMode, /fetchAiDocumentQueryPayload\(intent\)/)
  assert.match(aiMode, /buildAiDocumentQueryMessage/)
  assert.match(aiMode, /AI_DOCUMENT_QUERY_STEP_DELAY_MS/)
  assert.match(aiMode, /async function updateAiDocumentQueryThinking/)
  assert.match(aiMode, /解析自然语言筛选条件/)
  assert.match(aiMode, /查询业务单据接口/)
  assert.match(aiMode, /组合筛选单据/)
  assert.match(aiMode, /if \(await handleAiDocumentQueryIntent\(prompt, pendingMessage\)\) \{[\s\S]*return[\s\S]*\}/)
  assert.match(aiMode, /await ensureAiDocumentDetailStillAvailable\(detailRequest\)/)
  assert.match(aiMode, /emit\('open-document', detailRequest\)/)
})

test('AI mode asks for manual confirmation before generating application preview table', () => {
  assert.match(aiMode, /function buildAiRequiredApplicationGateSuggestedActions\(flow, prompt = ''\)/)
  assert.match(aiMode, /label:\s*'确认发起出差申请'/)
  assert.match(aiMode, /action_type:\s*'ai_application_start_inline'/)
  assert.match(aiMode, /carry_text:\s*prompt/)
  assert.match(aiMode, /label:\s*'确认关联已有申请单'/)
  assert.match(aiMode, /flow_id:\s*'travel_reimbursement'/)
  assert.match(aiMode, /suggestedActions:\s*requiredApplicationContinuationFlow[\s\S]*buildAiRequiredApplicationGateSuggestedActions\(requiredApplicationContinuationFlow, prompt\)/)
  assert.doesNotMatch(aiMode, /continueAiRequiredApplicationGateFromPlan\(normalizedPlan, prompt\)/)
  assert.doesNotMatch(aiMode, /flow\.flowId === 'travel_application'[\s\S]*void startAiApplicationPreview\('travel', '差旅费', prompt\)/)
  assert.match(aiMode, /class="workbench-ai-application-preview application-preview-shell"/)
  assert.match(aiMode, /resolveInlineApplicationPreviewRows\(message\)/)
  assert.match(aiMode, /commitInlineApplicationPreviewEditor\(message\)/)
})

test('AI mode shows pending feedback before async application preview estimate refresh', () => {
  const startPreviewFunction = applicationPreviewFlow.match(
    /async function startAiApplicationPreview[\s\S]*?\n  }\n\n  return \{/
  )?.[0] || ''

  assert.match(startPreviewFunction, /const pendingMessage = createInlineMessage\(\s*'assistant',\s*'正在生成申请核对表/)
  assert.ok(
    startPreviewFunction.indexOf('conversationMessages.value.push(pendingMessage)') <
      startPreviewFunction.indexOf('await refreshApplicationPreviewEstimate(')
  )
  assert.match(startPreviewFunction, /pending:\s*true/)
  assert.match(startPreviewFunction, /replaceInlineMessage\(\s*pendingMessage\.id/)
})

test('AI mode handles application preview save and submit through buttons or text commands', () => {
  assert.match(aiMode, /AI_APPLICATION_ACTION_SAVE_DRAFT/)
  assert.match(aiMode, /AI_APPLICATION_ACTION_SUBMIT/)
  assert.match(aiMode, /runAiApplicationPreviewAction/)
  assert.match(aiMode, /buildAiApplicationPrecheck/)
  assert.match(aiMode, /buildAiApplicationSubmitConflictMessage/)
  assert.match(aiMode, /isAiApplicationPrecheckBlocking/)
  assert.match(aiMode, /applicationSubmitConfirmOpen/)
  assert.match(aiMode, /确认直接提交申请/)
  assert.match(aiMode, /function buildInlineApplicationPreviewSuggestedActions\(applicationPreview = \{\}, draftPayload = null\)/)
  assert.match(aiMode, /label:\s*'直接提交'/)
  assert.match(aiMode, /function resolveInlineApplicationPreviewActionFromText\(text = ''\)/)
  assert.match(aiMode, /function executeInlineApplicationPreviewAction\(actionType, sourceMessage = null, options = \{\}\)/)
  assert.match(aiMode, /function confirmInlineApplicationSubmit\(\)/)
  assert.match(aiMode, /function cancelInlineApplicationSubmitConfirm\(\)/)
  assert.match(aiMode, /function handleInlineApplicationPreviewTextAction\(prompt, applicationPreviewEstimatePending\)/)
  assert.match(aiMode, /if \(applicationFlow\.handleInlineApplicationPreviewTextAction\(cleanPrompt, applicationPreviewEstimatePending\)\) \{[\s\S]*return[\s\S]*\}/)
  assert.match(aiMode, /\[AI_APPLICATION_ACTION_SAVE_DRAFT, AI_APPLICATION_ACTION_SUBMIT\]\.includes\(actionType\)/)
  assert.match(aiMode, /normalizedPreview\.readyToSubmit/)
  assert.match(aiMode, /fetchExpenseClaims\(\{ page: 1, pageSize: 100 \}\)/)
  assert.match(aiMode, /skipUserMessage/)
  assert.match(aiMode, /暂不能提交申请/)
  assert.match(aiMode, /#ai-open-application-detail:/)
})

test('AI mode keeps missing application fields editable in the preview table without quick template action', () => {
  assert.match(aiMode, /function buildInlineApplicationPreviewSuggestedActions\(applicationPreview = \{\}, draftPayload = null\)/)
  assert.match(aiMode, /label:\s*'保存草稿'/)
  assert.match(aiMode, /function handleInlineApplicationPreviewTextAction\(prompt, applicationPreviewEstimatePending\)/)
  assert.doesNotMatch(aiMode, /label:\s*'快速模板'/)
  assert.doesNotMatch(aiMode, /action_type:\s*'prefill_composer'/)
  assert.doesNotMatch(aiMode, /buildInlineApplicationPreviewTemplatePrefill/)
  assert.doesNotMatch(aiMode, /applyInlineApplicationPreviewTemplateText/)
})

test('AI mode confirms a new direct submit before adding the action while allowing contextual saved-draft submit', () => {
  const executeStart = applicationPreviewFlow.indexOf('async function executeInlineApplicationPreviewAction')
  const executeEnd = applicationPreviewFlow.indexOf('\n  function handleInlineApplicationPreviewTextAction', executeStart)
  const executeBlock = applicationPreviewFlow.slice(executeStart, executeEnd)
  const confirmGateIndex = executeBlock.indexOf('if (isSubmit && !options.confirmed && !shouldSubmitSavedDraftDirectly)')
  const requestConfirmIndex = executeBlock.indexOf('requestInlineApplicationSubmitConfirmation', confirmGateIndex)
  const confirmedActionPushIndex = executeBlock.indexOf('pushInlineApplicationActionUserMessage(userText)', requestConfirmIndex)

  assert.match(executeBlock, /const shouldSubmitSavedDraftDirectly = isSubmit/)
  assert.ok(confirmGateIndex >= 0, '非上下文草稿提交应先进入确认分支')
  assert.ok(requestConfirmIndex > confirmGateIndex, '直接提交确认分支应先打开确认弹窗')
  assert.ok(confirmedActionPushIndex > requestConfirmIndex, '确认弹窗打开前不应追加“直接提交”用户消息')
  assert.match(
    executeBlock,
    /requestInlineApplicationSubmitConfirmation\(targetMessage,\s*\{\s*\.\.\.options,\s*userText\s*\}\)/
  )

  const confirmStart = applicationPreviewFlow.indexOf('function confirmInlineApplicationSubmit()')
  const confirmEnd = applicationPreviewFlow.indexOf('\n  async function runInlineApplicationSubmitPrecheck', confirmStart)
  const confirmBlock = applicationPreviewFlow.slice(confirmStart, confirmEnd)
  assert.match(confirmBlock, /userText:\s*context\.userText \|\| '直接提交'/)
  assert.match(confirmBlock, /skipUserMessage:\s*false/)

  const cancelStart = applicationPreviewFlow.indexOf('function cancelInlineApplicationSubmitConfirm()')
  const cancelEnd = applicationPreviewFlow.indexOf('\n  function confirmInlineApplicationSubmit', cancelStart)
  const cancelBlock = applicationPreviewFlow.slice(cancelStart, cancelEnd)
  assert.doesNotMatch(cancelBlock, /pushInlineUserMessage|pushInlineApplicationActionUserMessage/)
})

test('AI mode formats saved application draft as a detail table without continuing submit flow', () => {
  assert.match(aiMode, /function buildInlineApplicationResultTable\(draftPayload = \{\}, options = \{\}\)/)
  assert.match(aiMode, /function normalizeInlineApplicationStatusLabel\(value, fallback = ''\)/)
  assert.equal(normalizeInlineApplicationStatusLabel('submitted'), '审批中')
  assert.match(aiMode, /const statusLabel = normalizeInlineApplicationStatusLabel\(info\.statusLabel, options\.statusLabel\)/)
  assert.match(aiMode, /\| 单据类型 \| 单据编号 \| 单据状态 \| 当前节点 \| 日期 \| 地点 \| 事由 \| 金额 \|/)
  assert.doesNotMatch(aiMode, /\| 单据类型 \| 单据编号 \| 单据状态 \| 当前节点 \| 日期 \| 地点 \| 事由 \| 金额 \| 操作 \|/)
  assert.doesNotMatch(aiMode, /\[查看\]\(\$\{href\}\)/)
  assert.match(aiMode, /dateLabel:\s*rangeText \|\| dateText \|\| resolveBodyField\(\['时间', '日期', '申请时间'\]\) \|\| '待补充'/)
  assert.match(aiMode, /locationLabel:[\s\S]*resolveBodyField\(\['地点', '目的地'\]\) \|\| '待补充'/)
  assert.match(aiMode, /reasonLabel:[\s\S]*resolveBodyField\(\['事由', '事件', '申请事由'\]\) \|\| '待补充'/)
  assert.match(aiMode, /function buildInlineApplicationDetailAction\(draftPayload = \{\}\)/)
  assert.match(aiMode, /action_type:\s*'open_application_detail'/)

  const resultStart = applicationPreviewModel.indexOf('function buildInlineApplicationPreviewActionResultText')
  const resultEnd = applicationPreviewModel.indexOf('\nexport function buildInlineApplicationDetailAction', resultStart)
  const resultBlock = applicationPreviewModel.slice(resultStart, resultEnd)
  const submitBranchIndex = resultBlock.indexOf('actionType === AI_APPLICATION_ACTION_SUBMIT')
  const saveBranchIndex = resultBlock.indexOf("'### 申请草稿已保存'")
  const saveBranch = resultBlock.slice(saveBranchIndex)

  assert.ok(submitBranchIndex >= 0)
  assert.ok(saveBranchIndex > submitBranchIndex, '保存草稿结果应走非提交分支')
  assert.match(
    saveBranch,
    /buildInlineApplicationResultTable\(draftPayload,\s*\{[\s\S]*statusLabel:\s*'草稿'[\s\S]*stageLabel:\s*'待提交'/
  )
  assert.doesNotMatch(saveBranch, /进入审批流程/)

  const executeStart = applicationPreviewFlow.indexOf('async function executeInlineApplicationPreviewAction')
  const executeEnd = applicationPreviewFlow.indexOf('\n  function handleInlineApplicationPreviewTextAction', executeStart)
  const executeBlock = applicationPreviewFlow.slice(executeStart, executeEnd)
  assert.match(executeBlock, /targetMessage\.suggestedActions = \[\]/)
  assert.doesNotMatch(
    executeBlock,
    /targetMessage\.suggestedActions = isSubmit[\s\S]*buildInlineApplicationPreviewSuggestedActions\(targetMessage\.applicationPreview, draftPayload\)/
  )
  assert.match(executeBlock, /const detailActions = buildInlineApplicationDetailAction\(draftPayload\)/)
  assert.match(executeBlock, /suggestedActions:\s*shouldAutoContinueNextTask[\s\S]*detailActions/)
})

test('AI mode locks application preview actions while estimate refresh is pending', () => {
  assert.match(aiMode, /function isApplicationPreviewEstimatePendingPreview\(applicationPreview = \{\}\)/)
  assert.match(
    aiMode,
    /function buildInlineApplicationPreviewSuggestedActions\(applicationPreview = \{\}, draftPayload = null\) \{[\s\S]*if \(isApplicationPreviewEstimatePendingPreview\(applicationPreview\)\) \{[\s\S]*return \[\]/
  )
  assert.match(aiMode, /const isAiModeInputLocked = computed\(\(\) => applicationPreviewEstimatePending\.value \|\| isAiModeReceiptRecognitionPending\.value\)/)
  assert.match(aiMode, /:disabled="isAiModeInputLocked"/)
  assert.match(aiMode, /v-if="canShowInlineSuggestedActions\(message\)"/)
  assert.match(aiMode, /:disabled="isInlineSuggestedActionDisabled\(action, message\)"/)
  assert.match(
    aiMode,
    /message\.suggestedActions = \[\][\s\S]*const committed = await commitBaseApplicationPreviewEditor\(message\)/
  )
  assert.match(
    aiMode,
    /if \(applicationPreviewEstimatePending\.value\) \{[\s\S]*toast\('请等待费用测算完成后再继续操作。'\)[\s\S]*return true/
  )
  assert.match(
    aiMode,
    /row\.editable && !isApplicationPreviewEstimatePending\(message\) \? 0 : -1/
  )
  assert.match(
    aiMode,
    /费用测算正在同步，请稍等，完成后才能保存草稿或直接提交。/
  )
})
