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

import {
  APPLICATION_WELCOME_QUICK_ACTIONS,
  APPROVAL_WELCOME_QUICK_ACTIONS,
  ASSISTANT_SESSION_MODE_OPTIONS,
  EXPENSE_WELCOME_QUICK_ACTIONS,
  SESSION_TYPE_APPLICATION,
  SESSION_TYPE_APPROVAL,
  SESSION_TYPE_EXPENSE,
  SESSION_TYPE_KNOWLEDGE,
  buildWelcomeQuickActions
} from '../src/views/scripts/travelReimbursementConversationModel.js'
import {
  GUIDED_ACTION_CONFIRM_REIMBURSEMENT_REVIEW,
  GUIDED_ACTION_CONTINUE_FILLING,
  GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR,
  GUIDED_ACTION_START_APPLICATION,
  GUIDED_ACTION_PROCESS_INTERRUPTION,
  GUIDED_ACTION_SELECT_EXPENSE_TYPE,
  GUIDED_ACTION_SELECT_REQUIRED_APPLICATION,
  GUIDED_ACTION_SELECT_QUERY_MODE,
  GUIDED_ACTION_SELECT_QUERY_STATUS,
  GUIDED_ACTION_START_REIMBURSEMENT,
  GUIDED_ACTION_START_STATUS_QUERY,
  GUIDED_FLOW_MODE_REIMBURSEMENT,
  GUIDED_FLOW_MODE_STATUS_QUERY,
  applyGuidedReimbursementAnswer,
  buildGuidedExpenseTypeActions,
  buildGuidedInterruptionActions,
  buildGuidedQueryModeActions,
  buildGuidedQueryStatusActions,
  buildGuidedReimbursementSummaryText,
  buildGuidedReviewConfirmationActions,
  buildGuidedReviewSubmitOptions,
  buildGuidedStatusQueryText,
  buildGuidedStepPromptText,
  createEmptyGuidedFlowState,
  createGuidedReimbursementState,
  createGuidedStatusQueryState,
  isGuidedReimbursementReadyForReview,
  normalizeGuidedFlowState,
  selectGuidedRequiredApplication,
  selectGuidedExpenseType,
  selectGuidedQueryMode,
  shouldConfirmGuidedInterruption,
  waitForGuidedApplicationSelection
} from '../src/views/scripts/travelReimbursementGuidedFlowModel.js'
import {
  buildRequiredApplicationActions,
  buildRequiredApplicationMissingText,
  buildRequiredApplicationSelectionText,
  filterRequiredApplicationCandidates,
  requiresApplicationBeforeReimbursement
} from '../src/views/scripts/travelReimbursementApplicationLinkModel.js'
import {
  ASSISTANT_SCOPE_ACTION_SWITCH,
  resolveAssistantScopeGuard
} from '../src/utils/assistantSessionScope.js'

const createViewScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/TravelReimbursementCreateView.js', import.meta.url)),
  'utf8'
)
const guidedFlowScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementGuidedFlow.js', import.meta.url)),
  'utf8'
)
const sessionStateScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSessionState.js', import.meta.url)),
  'utf8'
)
const submitComposerScript = readFileSync(
  fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)),
  'utf8'
)

test('assistant session modes expose independent quick actions', () => {
  assert.deepEqual(
    ASSISTANT_SESSION_MODE_OPTIONS.map((item) => item.label),
    ['申请助手', '报销助手', '审核助手', '财务知识助手', '预算编制助手']
  )
  assert.deepEqual(
    EXPENSE_WELCOME_QUICK_ACTIONS.map((item) => item.label),
    ['快速发起报销', '查询单据状态', '差旅计算器']
  )
  assert.deepEqual(
    EXPENSE_WELCOME_QUICK_ACTIONS.map((item) => item.action),
    [
      GUIDED_ACTION_START_REIMBURSEMENT,
      GUIDED_ACTION_START_STATUS_QUERY,
      GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR
    ]
  )
  assert.ok(EXPENSE_WELCOME_QUICK_ACTIONS.every((item) => !item.prompt))
  assert.equal(buildWelcomeQuickActions(SESSION_TYPE_EXPENSE).length, 3)
  assert.deepEqual(
    buildWelcomeQuickActions(SESSION_TYPE_APPLICATION).map((item) => item.label),
    APPLICATION_WELCOME_QUICK_ACTIONS.map((item) => item.label)
  )
  assert.equal(buildWelcomeQuickActions(SESSION_TYPE_APPLICATION)[0].action, GUIDED_ACTION_START_APPLICATION)
  assert.ok(!buildWelcomeQuickActions(SESSION_TYPE_APPLICATION)[0].prompt)
  assert.ok(
    buildWelcomeQuickActions(SESSION_TYPE_APPLICATION).every((item) => item.action !== GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR)
  )
  assert.ok(
    buildWelcomeQuickActions(SESSION_TYPE_APPROVAL).every((item) => item.action !== GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR)
  )
  assert.match(guidedFlowScript, /GUIDED_ACTION_START_APPLICATION/)
  assert.match(guidedFlowScript, /buildApplicationTemplatePreview/)
  assert.deepEqual(
    buildWelcomeQuickActions(SESSION_TYPE_APPROVAL).map((item) => item.label),
    APPROVAL_WELCOME_QUICK_ACTIONS.map((item) => item.label)
  )
  assert.notDeepEqual(
    buildWelcomeQuickActions(SESSION_TYPE_KNOWLEDGE).map((item) => item.label),
    EXPENSE_WELCOME_QUICK_ACTIONS.map((item) => item.label),
    'knowledge hot questions should stay independent'
  )
})

test('assistant session scope guard keeps business boundaries isolated', () => {
  const expenseInApplication = resolveAssistantScopeGuard('我想报销的士票', SESSION_TYPE_APPLICATION)
  assert.equal(expenseInApplication.targetSessionType, SESSION_TYPE_EXPENSE)
  assert.match(expenseInApplication.text, /申请助手/)
  assert.match(expenseInApplication.text, /报销助手/)
  assert.equal(expenseInApplication.suggestedActions[0].action_type, ASSISTANT_SCOPE_ACTION_SWITCH)
  assert.equal(expenseInApplication.suggestedActions[0].payload.session_type, SESSION_TYPE_EXPENSE)
  assert.equal(expenseInApplication.suggestedActions[0].payload.carry_text, '我想报销的士票')

  assert.equal(resolveAssistantScopeGuard('我想发起一笔费用申请', SESSION_TYPE_APPLICATION), null)
  assert.equal(
    resolveAssistantScopeGuard('帮我查询待我审核的单据', SESSION_TYPE_EXPENSE).targetSessionType,
    SESSION_TYPE_APPROVAL
  )
  assert.equal(
    resolveAssistantScopeGuard('差旅住宿标准是多少', SESSION_TYPE_EXPENSE).targetSessionType,
    SESSION_TYPE_KNOWLEDGE
  )
  assert.equal(
    resolveAssistantScopeGuard('报销标准是多少', SESSION_TYPE_EXPENSE).targetSessionType,
    SESSION_TYPE_KNOWLEDGE
  )
  assert.equal(
    resolveAssistantScopeGuard('解释这张单据酒店超标风险', SESSION_TYPE_EXPENSE, { hasActiveReviewPayload: true }),
    null
  )
})

test('guided reimbursement asks type first and walks travel fields in order', () => {
  const typeActions = buildGuidedExpenseTypeActions()
  assert.deepEqual(
    typeActions.map((action) => action.label),
    ['差旅费', '交通费', '住宿费', '业务招待费', '办公用品费', '其他费用']
  )
  assert.ok(typeActions.every((action) => action.action_type === GUIDED_ACTION_SELECT_EXPENSE_TYPE))

  let state = createGuidedReimbursementState()
  assert.equal(state.mode, GUIDED_FLOW_MODE_REIMBURSEMENT)
  assert.equal(state.stepKey, 'expense_type')

  state = selectGuidedExpenseType(state, 'travel')
  assert.equal(state.stepKey, 'reason')
  assert.match(buildGuidedStepPromptText(state), /第 1 步：事由/)

  state = applyGuidedReimbursementAnswer(state, '去上海支持上海电力部署项目')
  assert.equal(state.stepKey, 'location')
  state = applyGuidedReimbursementAnswer(state, '上海')
  assert.equal(state.stepKey, 'time_range')
  state = applyGuidedReimbursementAnswer(state, '2026-05-20 至 2026-05-23，出差 3 天')
  assert.equal(state.stepKey, 'amount')
  state = applyGuidedReimbursementAnswer(state, '待核算')
  assert.equal(state.stepKey, 'attachments')
  state = applyGuidedReimbursementAnswer(state, '稍后上传')

  assert.ok(isGuidedReimbursementReadyForReview(state))
  assert.match(buildGuidedReimbursementSummaryText(state), /已完成“差旅费”的引导填写/)
  assert.deepEqual(
    buildGuidedReviewConfirmationActions().map((action) => action.action_type),
    [GUIDED_ACTION_CONFIRM_REIMBURSEMENT_REVIEW]
  )

  const submitOptions = buildGuidedReviewSubmitOptions(state)
  assert.equal(submitOptions.systemGenerated, true)
  assert.equal(submitOptions.extraContext.expense_scene_selection.expense_type, 'travel')
  assert.equal(submitOptions.extraContext.review_form_values.expense_type, '差旅费')
  assert.match(submitOptions.rawText, /事由：去上海支持上海电力部署项目/)
  assert.match(submitOptions.rawText, /出差时间\/天数：2026-05-20 至 2026-05-23，出差 3 天/)
})

test('guided reimbursement requires application selection for travel and entertainment', () => {
  assert.equal(requiresApplicationBeforeReimbursement('travel'), true)
  assert.equal(requiresApplicationBeforeReimbursement('meal'), true)
  assert.equal(requiresApplicationBeforeReimbursement('transport'), false)

  const claimsPayload = {
    items: [
      {
        id: 'app-travel',
        claim_no: 'AP-202605-001',
        employee_name: '张小青',
        expense_type: 'travel_application',
        reason: '去上海支持项目部署',
        location: '上海',
        amount: 1800,
        status: 'approved',
        created_at: '2026-05-20T08:00:00Z'
      },
      {
        id: 'app-meal',
        claim_no: 'AP-202605-002',
        employee_name: '张小青',
        expense_type: 'expense_application',
        reason: '客户招待沟通项目',
        location: '武汉',
        amount: 600,
        status: 'submitted',
        created_at: '2026-05-21T08:00:00Z'
      },
      {
        id: 'app-draft',
        claim_no: 'AP-202605-003',
        employee_name: '张小青',
        expense_type: 'travel_application',
        reason: '草稿出差申请',
        status: 'draft'
      },
      {
        id: 'app-other-user',
        claim_no: 'AP-202605-004',
        employee_name: '李四',
        expense_type: 'travel_application',
        reason: '其他员工出差申请',
        status: 'approved'
      }
    ]
  }

  const currentUser = { name: '张小青', username: 'xiaoqing.zhang' }
  const travelApplications = filterRequiredApplicationCandidates(claimsPayload, 'travel', currentUser)
  assert.deepEqual(travelApplications.map((item) => item.claim_no), ['AP-202605-001'])
  assert.match(buildRequiredApplicationSelectionText('travel', travelApplications), /需要先关联对应的申请单/)
  assert.match(buildRequiredApplicationMissingText('meal'), /不能继续这类报销流程/)

  const mealApplications = filterRequiredApplicationCandidates(claimsPayload, 'meal', currentUser)
  assert.deepEqual(mealApplications.map((item) => item.claim_no), ['AP-202605-002'])

  const actions = buildRequiredApplicationActions(travelApplications, GUIDED_ACTION_SELECT_REQUIRED_APPLICATION)
  assert.equal(actions[0].action_type, GUIDED_ACTION_SELECT_REQUIRED_APPLICATION)
  assert.equal(actions[0].payload.application_claim_no, 'AP-202605-001')

  let state = waitForGuidedApplicationSelection(createGuidedReimbursementState(), 'travel', travelApplications)
  assert.equal(state.stepKey, 'application_selection')
  assert.equal(state.applicationCandidates[0].claim_no, 'AP-202605-001')

  state = selectGuidedRequiredApplication(state, actions[0].payload)
  assert.equal(state.stepKey, 'reason')
  assert.equal(state.values.application_claim_no, 'AP-202605-001')
  assert.match(buildGuidedReimbursementSummaryText(state), /关联申请单：AP-202605-001/)

  const submitOptions = buildGuidedReviewSubmitOptions(state)
  assert.equal(submitOptions.extraContext.review_form_values.application_claim_no, 'AP-202605-001')
  assert.equal(submitOptions.extraContext.expense_scene_selection.application_claim_no, 'AP-202605-001')
  assert.match(submitOptions.rawText, /关联申请单：AP-202605-001/)
})

test('guided reimbursement interrupts suspicious questions before expensive flow', () => {
  const state = selectGuidedExpenseType(createGuidedReimbursementState(), 'transport')
  assert.equal(shouldConfirmGuidedInterruption('送客户去机场', state), false)
  assert.equal(shouldConfirmGuidedInterruption('帮我查询一下上周的报销状态？', state), true)
  assert.deepEqual(
    buildGuidedInterruptionActions().map((action) => action.action_type),
    [GUIDED_ACTION_CONTINUE_FILLING, GUIDED_ACTION_PROCESS_INTERRUPTION]
  )
})

test('status query guide collects a query mode before calling existing query flow', () => {
  let state = createGuidedStatusQueryState()
  assert.equal(state.mode, GUIDED_FLOW_MODE_STATUS_QUERY)
  assert.equal(state.stepKey, 'query_mode')
  assert.ok(buildGuidedQueryModeActions().every((action) => action.action_type === GUIDED_ACTION_SELECT_QUERY_MODE))

  state = selectGuidedQueryMode(state, 'status')
  assert.equal(state.stepKey, 'status_value')
  assert.ok(buildGuidedQueryStatusActions().every((action) => action.action_type === GUIDED_ACTION_SELECT_QUERY_STATUS))
  assert.equal(buildGuidedStatusQueryText(state, '已归档'), '帮我查询已归档的报销单据，筛选最近的 5 条记录')

  const keywordState = selectGuidedQueryMode(createGuidedStatusQueryState(), 'keyword')
  assert.equal(keywordState.stepKey, 'query_value')
  assert.equal(
    buildGuidedStatusQueryText(keywordState, '上海电力'),
    '帮我查询地点或事由包含“上海电力”的报销单据状态，筛选最近的 5 条记录'
  )
})

test('guided flow state is serializable and restored through session state', () => {
  const empty = createEmptyGuidedFlowState()
  assert.deepEqual(normalizeGuidedFlowState({ mode: 'bad' }), empty)
  assert.deepEqual(
    normalizeGuidedFlowState({
      mode: GUIDED_FLOW_MODE_REIMBURSEMENT,
      stepKey: 'amount',
      expenseType: 'travel',
      values: {
        amount: 200,
        attachment_names: ['a.pdf', '', 'a.pdf']
      },
      pendingInterruptionText: '查询状态？'
    }),
    {
      mode: GUIDED_FLOW_MODE_REIMBURSEMENT,
      stepKey: 'amount',
      expenseType: 'travel',
      values: {
        amount: '200',
        attachment_names: ['a.pdf']
      },
      pendingInterruptionText: '查询状态？',
      applicationCandidates: []
    }
  )

  assert.match(sessionStateScript, /guidedFlowState:\s*normalizeGuidedFlowState\(state\.guidedFlowState\)/)
  assert.match(sessionStateScript, /runtimeRefs\.guidedFlowState\?\.value/)
  assert.match(sessionStateScript, /guidedFlowState,\s*\n\s*insightPanelCollapsed/)
  assert.match(sessionStateScript, /function refreshWelcomeQuickActions/)
  assert.match(sessionStateScript, /buildWelcomeQuickActions\(/)
  assert.match(sessionStateScript, /resolveAccessibleSessionTypes\(\)\.reduce/)
  assert.match(sessionStateScript, /props\.entrySource === 'application' \? SESSION_TYPE_APPLICATION : SESSION_TYPE_EXPENSE/)
  assert.match(sessionStateScript, /const canRestorePersistedInitialState =[\s\S]*shouldPersistLocalSnapshot/)
})

test('guided flow is local until final confirmation or collected query handoff', () => {
  assert.doesNotMatch(guidedFlowScript, /runOrchestrator/)
  assert.doesNotMatch(guidedFlowScript, /startExpenseClaimDraftFlowStep/)
  assert.doesNotMatch(guidedFlowScript, /review_action:\s*['"]save_draft['"]/)
  assert.match(guidedFlowScript, /fetchExpenseClaims/)
  assert.match(guidedFlowScript, /GUIDED_ACTION_SELECT_REQUIRED_APPLICATION/)
  assert.match(guidedFlowScript, /handleSceneSelectionApplicationGate/)
  assert.match(createViewScript, /handleSceneSelectionApplicationGate/)
  assert.match(createViewScript, /if \(await handleGuidedComposerSubmit\(options\)\) \{[\s\S]*return null[\s\S]*\}[\s\S]*return submitComposerInternal\(options\)/)
  assert.match(createViewScript, /ASSISTANT_SCOPE_ACTION_SWITCH/)
  assert.match(createViewScript, /actionPayload\.carry_text/)
  assert.match(submitComposerScript, /resolveAssistantScopeGuard/)
  assert.match(submitComposerScript, /skipScopeGuard/)
  assert.match(guidedFlowScript, /submitExistingComposer\(submitOptions\)/)
  assert.match(guidedFlowScript, /submitExistingComposer\(\{[\s\S]*pendingText:\s*'正在查询单据状态\.\.\.'/)
})
