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

import {
  buildOperationFeedbackPayload,
  normalizeOperationFeedbackContext
} from '../src/composables/useOperationFeedback.js'

import {
  SESSION_TYPE_APPLICATION,
  SESSION_TYPE_EXPENSE,
  SESSION_TYPE_KNOWLEDGE,
  buildMessageMeta,
  buildWelcomeInsight,
  buildWelcomeMessage,
  createMessage,
  filterVisibleMessageMeta
} from '../src/views/scripts/travelReimbursementConversationModel.js'

const appShellRouteView = readFileSync(
  fileURLToPath(new URL('../src/views/AppShellRouteView.vue', import.meta.url)),
  'utf8'
)
const appShellComposable = readFileSync(
  fileURLToPath(new URL('../src/composables/useAppShell.js', import.meta.url)),
  'utf8'
)
const requestsComposable = readFileSync(
  fileURLToPath(new URL('../src/composables/useRequests.js', import.meta.url)),
  'utf8'
)
const assistantScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/TravelReimbursementCreateView.js', import.meta.url)),
  'utf8'
)
const assistantCreateStateScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementCreateViewState.js', import.meta.url)),
  'utf8'
)
const assistantCreateLifecycleScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementCreateViewLifecycle.js', import.meta.url)),
  'utf8'
)
const assistantCreateControlsScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementCreateViewControls.js', import.meta.url)),
  'utf8'
)
const assistantMessageActionsScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementMessageActions.js', import.meta.url)),
  'utf8'
)
const assistantSubmitComposerScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)),
  'utf8'
)
const assistantSubmitResponseModelScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/travelReimbursementSubmitResponseModel.js', import.meta.url)),
  'utf8'
)
const assistantSessionStateScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSessionState.js', import.meta.url)),
  'utf8'
)
const assistantSurface = [
  assistantScript,
  assistantCreateStateScript,
  assistantCreateLifecycleScript,
  assistantCreateControlsScript,
  assistantMessageActionsScript,
  assistantSubmitComposerScript,
  assistantSubmitResponseModelScript,
  assistantSessionStateScript
].join('\n')
const assistantTemplate = 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 chatViewTemplate = readFileSync(
  fileURLToPath(new URL('../src/views/ChatView.vue', import.meta.url)),
  'utf8'
)
test('application and reimbursement entries open the same financial assistant modal', () => {
  assert.match(appShellRouteView, /<TravelReimbursementCreateView[\s\S]*:entry-source="smartEntryContext\.source"/)
  assert.match(appShellRouteView, /@create-request="openTravelCreate"/)
  assert.match(appShellRouteView, /@create-application="openExpenseApplicationCreate"/)
  assert.match(appShellRouteView, /@new-application="openExpenseApplicationCreate"/)
  assert.doesNotMatch(appShellRouteView, /ExpenseApplicationDialog/)
})

test('documents center reloads immediately when entered or clicked again', () => {
  assert.match(appShellRouteView, /:refresh-token="documentCenterRefreshToken"/)
  assert.match(appShellRouteView, /@reload="reloadRequests"/)
  assert.match(appShellComposable, /const documentCenterRefreshToken = ref\(0\)/)
  assert.match(appShellComposable, /async function reloadDocumentCenterRequests\(\) \{[\s\S]*documentCenterRefreshToken\.value \+= 1[\s\S]*return reloadRequests\(\)/)
  assert.match(appShellComposable, /if \(view === 'documents'\) \{[\s\S]*void reloadDocumentCenterRequests\(\)/)
  assert.match(appShellComposable, /shouldRefreshCurrentDocumentCenter[\s\S]*route\.name === 'app-documents'[\s\S]*void reloadDocumentCenterRequests\(\)/)
  assert.match(appShellComposable, /documentCenterRefreshToken,/)
  assert.match(appShellComposable, /reloadDocumentCenterRequests,/)
})

test('documents center uses the full request list instead of the global date-filtered list', () => {
  assert.match(
    appShellRouteView,
    /<DocumentsCenterView[\s\S]*:filtered-requests="requests"[\s\S]*:has-data="requests\.length > 0"/
  )
  assert.doesNotMatch(
    appShellRouteView,
    /<DocumentsCenterView[\s\S]*:filtered-requests="filteredRequests"/
  )
})

test('workbench summary merges approval inbox requests without polluting document center rows', () => {
  assert.match(
    appShellComposable,
    /import \{\s*REIMBURSEMENT_LIST_PREVIEW_PARAMS,\s*extractExpenseClaimItems,\s*fetchApprovalExpenseClaims,\s*fetchExpenseClaimDetail\s*\} from '\.\.\/services\/reimbursements\.js'/
  )
  assert.match(appShellComposable, /const workbenchApprovalRequests = ref\(\[\]\)/)
  assert.match(appShellComposable, /async function reloadWorkbenchApprovalRequests\(\)/)
  assert.match(appShellComposable, /async function reloadWorkbenchRequests\(\)/)
  assert.match(appShellComposable, /fetchApprovalExpenseClaims\(REIMBURSEMENT_LIST_PREVIEW_PARAMS\)/)
  assert.match(appShellComposable, /extractExpenseClaimItems\(payload\)\.map\(\(item\) => mapExpenseClaimToRequest\(item\)\)/)
  assert.doesNotMatch(appShellComposable, /fetchAllApprovalExpenseClaims\(\)/)
  assert.match(appShellComposable, /Promise\.all\(\[[\s\S]*reloadRequests\(\{ silent: true \}\),[\s\S]*reloadWorkbenchApprovalRequests\(\)[\s\S]*\]\)/)
  assert.match(appShellComposable, /if \(view === 'workbench'\) \{[\s\S]*void reloadWorkbenchRequests\(\)/)
  assert.match(appShellComposable, /const workbenchRequests = computed\(\(\) =>[\s\S]*mergeWorkbenchRequests\(requests\.value, workbenchApprovalRequests\.value\)/)
  assert.match(appShellComposable, /buildWorkbenchSummary\(workbenchRequests\.value, currentUser\.value\)/)
  assert.match(appShellRouteView, /<DocumentsCenterView[\s\S]*:filtered-requests="requests"/)
  assert.doesNotMatch(appShellRouteView, /<DocumentsCenterView(?:(?!\/>)[\s\S])*workbenchRequests/)
})

test('workbench progress refreshes after homepage create or detail updates', () => {
  assert.match(appShellComposable, /async function handleDraftSaved\(payload = \{\}\) \{[\s\S]*await reloadWorkbenchRequests\(\)/)
  assert.match(appShellComposable, /async function handleRequestUpdated\(payload = \{\}\) \{[\s\S]*await reloadWorkbenchRequests\(\)[\s\S]*await refreshSelectedRequestDetail\(claimId\)/)
  assert.doesNotMatch(appShellComposable, /async function handleRequestUpdated\(payload = \{\}\) \{[\s\S]*await reloadRequests\(\)[\s\S]*await refreshSelectedRequestDetail/)
})

test('workbench progress refresh is silent to avoid homepage flashing', () => {
  assert.match(requestsComposable, /async function reload\(options = \{\}\) \{[\s\S]*const silent = Boolean\(options\?\.silent\)/)
  assert.match(requestsComposable, /if \(!silent\) \{[\s\S]*loading\.value = true[\s\S]*error\.value = ''[\s\S]*\}/)
  assert.match(requestsComposable, /catch \(nextError\) \{[\s\S]*if \(!silent\) \{[\s\S]*requests\.value = \[\][\s\S]*\}/)
  assert.match(requestsComposable, /finally \{[\s\S]*if \(!silent\) \{[\s\S]*loading\.value = false[\s\S]*\}/)
  assert.match(appShellComposable, /async function reloadWorkbenchRequests\(\) \{[\s\S]*reloadRequests\(\{ silent: true \}\)/)
  assert.match(appShellComposable, /async function reloadDocumentCenterRequests\(\) \{[\s\S]*return reloadRequests\(\)/)
})

test('document detail navigation preserves document center list query', () => {
  assert.match(
    appShellComposable,
    /function openRequestDetail\(request, options = \{\}\) \{[\s\S]*const requestId = resolveRequestDetailLookupId\(request\)[\s\S]*name: 'app-document-detail'[\s\S]*params: \{ requestId \},[\s\S]*query: buildDocumentDetailQuery\(options\)/
  )
  assert.match(
    appShellComposable,
    /function closeRequestDetail\(\) \{[\s\S]*router\.push\(\{ name: 'app-documents', query: buildDocumentReturnQuery\(\) \}\)/
  )
  assert.match(
    appShellComposable,
    /async function handleRequestDeleted\(payload = \{\}\) \{[\s\S]*router\.push\(\{ name: 'app-documents', query: buildDocumentReturnQuery\(\) \}\)/
  )
})

test('document detail refreshes claim detail instead of relying on stale list cache', () => {
  assert.match(
    appShellComposable,
    /import \{\s*REIMBURSEMENT_LIST_PREVIEW_PARAMS,\s*extractExpenseClaimItems,\s*fetchApprovalExpenseClaims,\s*fetchExpenseClaimDetail\s*\} from '\.\.\/services\/reimbursements\.js'/
  )
  assert.match(appShellComposable, /import \{ mapExpenseClaimToRequest, useRequests \} from '\.\/useRequests\.js'/)
  assert.match(appShellComposable, /const snapshot = normalizeRequestForUi\(selectedRequestSnapshot\.value\)[\s\S]*if \(isSameRequestIdentity\(snapshot, requestId\)\) \{[\s\S]*return snapshot/)
  assert.match(appShellComposable, /async function refreshSelectedRequestDetail\(requestOrId = selectedRequestSnapshot\.value\) \{[\s\S]*fetchExpenseClaimDetail\(lookupId\)[\s\S]*mapExpenseClaimToRequest\(payload\)[\s\S]*upsertRequestSnapshot\(mappedRequest\)/)
  assert.match(appShellComposable, /function isDetailLookupOnlyPayload\(payload = \{\}\) \{[\s\S]*payload\?\.detailLookupOnly/)
  assert.match(appShellComposable, /function openRequestDetail\(request, options = \{\}\) \{[\s\S]*selectedRequestSnapshot\.value = isDetailLookupOnlyRequest \? null : request \|\| null[\s\S]*void refreshSelectedRequestDetail\(isDetailLookupOnlyRequest \? requestId : request\)/)
  assert.match(appShellComposable, /async function handleRequestUpdated\(payload = \{\}\) \{[\s\S]*await reloadWorkbenchRequests\(\)[\s\S]*await refreshSelectedRequestDetail\(claimId\)/)
  assert.match(appShellComposable, /route\.name === 'app-document-detail'[\s\S]*void refreshSelectedRequestDetail\(String\(route\.params\.requestId \|\| ''\)\)/)
})

test('application entry keeps its own assistant source without creating a separate dialog', () => {
  assert.match(appShellComposable, /const SMART_ENTRY_SOURCE_APPLICATION = 'application'/)
  assert.match(appShellComposable, /function openExpenseApplicationCreate\(\) \{[\s\S]*openFinancialAssistantCreate\(SMART_ENTRY_SOURCE_APPLICATION\)/)
  assert.match(appShellComposable, /function openTravelCreate\(\) \{[\s\S]*openFinancialAssistantCreate\(SMART_ENTRY_SOURCE_REIMBURSEMENT\)/)
  assert.match(appShellComposable, /openExpenseApplicationCreate,/)
  assert.match(assistantSurface, /activeSessionType\.value === SESSION_TYPE_APPLICATION[\s\S]*我想先申请一笔差旅费用/)
})

test('application edit prefill opens assistant without auto submit', () => {
  assert.match(appShellRouteView, /:initial-prompt-auto-submit="smartEntryContext\.initialPromptAutoSubmit"/)
  assert.match(appShellRouteView, /:initial-application-preview="smartEntryContext\.initialApplicationPreview"/)
  assert.match(appShellRouteView, /:initial-draft-payload="smartEntryContext\.initialDraftPayload"/)
  assert.match(appShellComposable, /initialPromptAutoSubmit:\s*true/)
  assert.match(appShellComposable, /initialApplicationPreview:\s*null/)
  assert.match(appShellComposable, /initialDraftPayload:\s*null/)
  assert.match(appShellComposable, /initialPromptAutoSubmit:\s*payload\.initialPromptAutoSubmit !== false/)
  assert.match(appShellComposable, /initialApplicationPreview:\s*payload\.applicationPreview && typeof payload\.applicationPreview === 'object'/)
  assert.match(appShellComposable, /initialDraftPayload:\s*payload\.draftPayload && typeof payload\.draftPayload === 'object'/)
  assert.match(
    assistantScript,
    /initialPromptAutoSubmit:\s*\{[\s\S]*type:\s*Boolean[\s\S]*default:\s*true/
  )
  assert.match(
    assistantScript,
    /initialApplicationPreview:\s*\{[\s\S]*type:\s*Object[\s\S]*default:\s*null/
  )
  assert.match(
    assistantScript,
    /initialDraftPayload:\s*\{[\s\S]*type:\s*Object[\s\S]*default:\s*null/
  )
  assert.match(
    assistantSurface,
    /props\.initialApplicationPreview[\s\S]*normalizeApplicationPreview\(props\.initialApplicationPreview\)[\s\S]*const draftPayload = props\.initialDraftPayload[\s\S]*createMessage\('assistant', buildLocalApplicationPreviewMessage\(applicationPreview\)[\s\S]*draftPayload/
  )
  assert.match(assistantSessionStateScript, /&& !props\.initialApplicationPreview/)
  assert.match(
    assistantSurface,
    /if \(props\.initialPromptAutoSubmit !== false\) \{[\s\S]*submitComposer\(\)/
  )
})

test('financial assistant toolbar renders isolated assistant sessions without steward entry', () => {
  assert.match(assistantSurface, /filterAssistantSessionModes\(ASSISTANT_SESSION_MODE_OPTIONS, currentUser\.value\)/)
  assert.match(assistantSurface, /\.filter\(\(mode\) => mode\.key !== SESSION_TYPE_STEWARD\)/)
  assert.match(assistantSurface, /mode\.key === SESSION_TYPE_BUDGET/)
  assert.match(assistantSurface, /visibleModes\.map/)
  assert.match(assistantSurface, /targetSessionType:\s*mode\.key/)
  assert.match(assistantSurface, /active:\s*mode\.key === activeSessionType\.value/)
  assert.match(assistantTemplate, /:class="\{ active: shortcut\.active \}"/)
  assert.match(assistantTemplate, /:aria-pressed="shortcut\.active \? 'true' : 'false'"/)
  assert.match(assistantTemplate, /:disabled="shortcut\.active \|\| submitting/)
})

test('closing a busy assistant keeps the running instance recoverable', () => {
  assert.match(appShellRouteView, /:reopen-token="smartEntryRevealToken"/)
  assert.match(appShellComposable, /const smartEntryRevealToken = ref\(0\)/)
  assert.match(appShellComposable, /if \(smartEntryOpen\.value\) \{\s*smartEntryRevealToken\.value \+= 1\s*return\s*\}/)
  assert.match(appShellComposable, /smartEntryRevealToken,/)
  assert.match(assistantScript, /reopenToken:\s*\{\s*type:\s*Number/)
  assert.match(assistantSurface, /closeAfterBusy\.value = false[\s\S]*workbenchVisible\.value = true/)
  assert.match(assistantSurface, /function emitCloseAfterLeave\(\) \{\s*if \(workbenchVisible\.value\)/)
})

test('financial assistant welcome copy differentiates application intent from reimbursement entry', () => {
  const user = { name: '李文静', username: 'wenjing.li', grade: 'P5' }
  const applicationWelcome = buildWelcomeMessage('application', null, SESSION_TYPE_APPLICATION, user)
  const reimbursementWelcome = buildWelcomeMessage('topbar', null, SESSION_TYPE_EXPENSE, user)
  const knowledgeWelcome = buildWelcomeMessage('topbar', null, SESSION_TYPE_KNOWLEDGE, user)
  const applicationInsight = buildWelcomeInsight('application', null, SESSION_TYPE_APPLICATION, user)

  assert.match(applicationWelcome, /申请助手/)
  assert.match(applicationWelcome, /费用申请、报销申请还是其他财务事项/)
  assert.match(reimbursementWelcome, /报销助手/)
  assert.match(reimbursementWelcome, /报销发起、票据识别、草稿归集、报销信息核对/)
  assert.match(knowledgeWelcome, /财务知识助手/)
  assert.notEqual(applicationWelcome, reimbursementWelcome)
  assert.equal(applicationInsight.metricValue, '申请助手')
  assert.equal(applicationInsight.title, '申请助手')
})

test('assistant message meta hides internal routing and permission chips', () => {
  const meta = buildMessageMeta(
    {
      selected_agent: 'user_agent',
      permission_level: 'draft_write',
      run_id: 'run-001',
      trace_summary: {
        tool_count: 3,
        degraded: true
      },
      requires_confirmation: true
    },
    ['invoice.pdf']
  )

  assert.deepEqual(meta, ['已降级', '待确认', '附件: 1'])
  assert.deepEqual(
    filterVisibleMessageMeta(['Agent: user_agent', '权限: draft_write', 'Run: run-001', '工具: 3', '等待确认']),
    ['等待确认']
  )
  assert.deepEqual(
    createMessage('assistant', '测试', [], { meta: ['Agent: user_agent', '权限: draft_write', '处理中'] }).meta,
    ['处理中']
  )
  assert.doesNotMatch(messageItemTemplate, /message-meta-row|message-meta-chip/)
  assert.doesNotMatch(chatViewTemplate, /agent-meta-row|agent-meta-chip/)
})

test('assistant message action toolbar collects lightweight feedback', () => {
  assert.doesNotMatch(appShellRouteView, /<OperationFeedbackDialog/)
  assert.doesNotMatch(appShellRouteView, /@operation-completed="handleOperationCompleted"/)
  assert.doesNotMatch(appShellComposable, /useOperationFeedback/)
  assert.doesNotMatch(messageItemTemplate, /<OperationFeedbackInlineCard/)
  assert.match(messageItemTemplate, /class="message-action-toolbar"/)
  assert.match(messageItemTemplate, /ui\.shouldShowAssistantMessageActions\(message\)/)
  assert.match(messageItemTemplate, /ui\.copyAssistantMessage\(message\)/)
  assert.match(messageItemTemplate, /ui\.speakAssistantMessage\(message\)/)
  assert.match(messageItemTemplate, /rating:\s*5,\s*reason:\s*'thumbs_up'/)
  assert.match(messageItemTemplate, /rating:\s*1,\s*reason:\s*'thumbs_down'/)
  assert.match(messageItemTemplate, /mdi mdi-content-copy/)
  assert.match(messageItemTemplate, /mdi mdi-volume-high/)
  assert.match(messageItemTemplate, /mdi mdi-thumb-up-outline/)
  assert.match(messageItemTemplate, /mdi mdi-thumb-down-outline/)
  assert.match(assistantScript, /emits:\s*\['close', 'draft-saved', 'request-updated'\]/)
  assert.match(appShellRouteView, /@request-updated="handleRequestUpdated"/)
  assert.match(assistantSurface, /function buildMessageOperationFeedbackContext/)
  assert.match(assistantSurface, /source:\s*'assistant_message_action'/)
  assert.match(assistantSurface, /operation_type:\s*message\?\.stewardPlan \? 'steward_message' : 'assistant_message'/)
  assert.match(assistantSurface, /function shouldShowAssistantMessageActions/)
  assert.match(assistantSurface, /function copyAssistantMessage/)
  assert.match(assistantSurface, /function speakAssistantMessage/)
  assert.match(assistantSurface, /function isMessageFeedbackSelected/)
  assert.match(assistantSurface, /function submitOperationFeedbackForMessage/)
  assert.match(assistantSurface, /createOperationFeedback/)
  assert.match(assistantSurface, /normalizeOperationFeedbackContext/)
  assert.match(assistantSurface, /submitted:\s*true/)
  assert.match(assistantSurface, /dismissed:\s*false/)
  assert.doesNotMatch(assistantSurface, /emit\('operation-completed'/)
  assert.match(assistantSubmitComposerScript, /emitOperationCompleted\?\.\(payload/)
  assert.match(assistantSubmitComposerScript, /operationFeedback:\s*buildOperationFeedbackState/)
  assert.match(assistantSubmitResponseModelScript, /rating:\s*0/)

  const context = normalizeOperationFeedbackContext(
    {
      run_id: 'run-001',
      conversation_id: 'conv-001',
      selected_agent: 'user_agent',
      session_type: 'application',
      operation_status: 'succeeded',
      route_reason: 'model_route',
      result: { answer: '处理完成' }
    },
    { username: 'wenjing.li' }
  )
  const payload = buildOperationFeedbackPayload(context, { rating: 2, reason: '识别错了' })

  assert.equal(context.runId, 'run-001')
  assert.equal(context.userId, 'wenjing.li')
  assert.equal(payload.run_id, 'run-001')
  assert.equal(payload.conversation_id, 'conv-001')
  assert.equal(payload.agent, 'user_agent')
  assert.equal(payload.rating, 2)
  assert.equal(payload.reason, '识别错了')
  assert.equal(payload.context_json.low_rating, true)
})
