import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import test from 'node:test'

import {
  buildLinkedDraftRunningText,
  useWorkbenchAiExpenseFlow
} from '../src/composables/workbenchAiMode/useWorkbenchAiExpenseFlow.js'
import {
  CONTINUE_REIMBURSEMENT_DRAFT_ACTION,
  CREATE_STANDALONE_REIMBURSEMENT_DRAFT_ACTION
} from '../src/views/scripts/travelReimbursementAssociationGateModel.js'

const personalWorkbenchAiMode = readFileSync(
  join(process.cwd(), 'web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js'),
  'utf8'
)

function createInlineMessage(role, content, extras = {}) {
  return {
    id: `${role}-${Math.random().toString(16).slice(2)}`,
    role,
    content,
    text: content,
    ...extras
  }
}

function buildFlow(options = {}) {
  const conversationMessages = { value: [] }
  const aiExpenseDraft = { value: null }
  const activated = []
  let persisted = 0
  const scrolled = []

  const flow = useWorkbenchAiExpenseFlow({
    activateInlineConversation: (payload) => {
      activated.push(payload)
      conversationStarted.value = true
    },
    aiExpenseDraft,
    assistantDraft: { value: '我要报销' },
    clearAiModeFiles: () => {},
    closeWorkbenchDatePicker: () => {},
    conversationMessages,
    conversationStarted,
    createInlineMessage,
    currentUser: { value: options.currentUser || { name: '张小青', username: 'xiaoqing.zhang' } },
    createLinkedReimbursementDraftJobForAi: options.createLinkedReimbursementDraftJobForAi,
    fetchExpenseClaimsForAi: options.fetchExpenseClaimsForAi,
    fetchLinkedReimbursementDraftJobForAi: options.fetchLinkedReimbursementDraftJobForAi,
    linkedDraftJobPollIntervalMs: options.linkedDraftJobPollIntervalMs ?? 0,
    linkedDraftJobMaxPolls: options.linkedDraftJobMaxPolls ?? 2,
    runOrchestratorForAi: options.runOrchestratorForAi,
    associationQueryTimeoutMs: options.associationQueryTimeoutMs,
    persistCurrentConversation: () => {
      persisted += 1
    },
    pushInlineUserMessage: (text) => {
      conversationMessages.value.push(createInlineMessage('user', text))
    },
    removeWorkbenchDateTag: () => {},
    resolveLatestInlineUserPrompt: () => '我要报销',
    scrollInlineConversationToBottom: (payload) => {
      scrolled.push(payload || {})
    },
    startAiApplicationPreview: () => {}
  })
  return { activated, aiExpenseDraft, conversationMessages, flow, get persisted() { return persisted }, scrolled }
}

const conversationStarted = { value: false }

test('linked reimbursement draft running text avoids duplicate status wording', () => {
  const content = buildLinkedDraftRunningText(
    { message: '正在后台生成报销草稿...' },
    'AVF9ST8TT'
  )

  const repeated = content.match(/正在后台生成报销草稿/g) || []
  assert.equal(repeated.length, 1)
  assert.doesNotMatch(content, /处理状态：\s*正在后台生成报销草稿/)
  assert.match(content, /回来后我会继续查询任务结果/)
})

test('reimbursement intent checks drafts before recommending approved application documents', async () => {
  conversationStarted.value = false
  let queried = 0
  const { conversationMessages, flow } = buildFlow({
    fetchExpenseClaimsForAi: async () => {
      queried += 1
      return {
        items: [
          {
            id: 'app-travel-1',
            claim_no: 'AP-202606-001',
            employee_name: '张小青',
            expense_type: 'travel_application',
            reason: '北京客户现场实施',
            location: '北京',
            status: 'approved',
            start_date: '2026-06-23',
            end_date: '2026-06-25',
            amount: 1650
          },
          {
            id: 're-linked-1',
            claim_no: 'RE-202606-001',
            employee_name: '张小青',
            expense_type: 'travel',
            status: 'submitted',
            risk_flags_json: [{
              source: 'application_link',
              application_claim_no: 'AP-202606-002'
            }]
          },
          {
            id: 'app-linked-1',
            claim_no: 'AP-202606-002',
            employee_name: '张小青',
            expense_type: 'travel_application',
            reason: '已被关联的申请',
            status: 'approved'
          }
        ]
      }
    }
  })

  await flow.startAiReimbursementAssociationGate('我要报销')

  assert.equal(queried, 1)
  assert.equal(conversationMessages.value[0]?.role, 'user')
  assert.equal(conversationMessages.value[0]?.content, '我要报销')
  const assistantMessage = conversationMessages.value.at(-1)
  assert.equal(assistantMessage.role, 'assistant')
  assert.match(assistantMessage.content, /先检查.*报销草稿/)
  assert.match(assistantMessage.content, /没有查到可继续的报销草稿/)
  assert.match(assistantMessage.content, /先查询.*可关联申请单/)
  assert.match(assistantMessage.content, /AP-202606-001/)
  assert.doesNotMatch(assistantMessage.content, /AP-202606-002/)
  assert.equal(assistantMessage.suggestedActions[0].action_type, 'select_required_application')
  assert.equal(assistantMessage.suggestedActions[0].payload.application_claim_no, 'AP-202606-001')
  assert.equal(assistantMessage.suggestedActions.at(-1).action_type, 'skip_required_application_link')
})

test('reimbursement intent stops at existing reimbursement drafts before application association', async () => {
  conversationStarted.value = false
  const { conversationMessages, flow } = buildFlow({
    fetchExpenseClaimsForAi: async () => ({
      items: [
        {
          id: 'draft-travel-1',
          claim_no: 'RE-202606-010',
          employee_name: '张小青',
          expense_type: 'travel',
          reason: '北京客户现场实施报销',
          location: '北京',
          status: 'draft',
          amount: '0.00',
          created_at: '2026-06-23T10:00:00Z'
        },
        {
          id: 'app-travel-1',
          claim_no: 'AP-202606-001',
          employee_name: '张小青',
          expense_type: 'travel_application',
          reason: '北京客户现场实施',
          location: '北京',
          status: 'approved',
          start_date: '2026-06-23',
          end_date: '2026-06-25',
          amount: 1650
        }
      ]
    })
  })

  await flow.startAiReimbursementAssociationGate('我要报销')

  const assistantMessage = conversationMessages.value.at(-1)
  assert.match(assistantMessage.content, /先检查.*报销草稿/)
  assert.match(assistantMessage.content, /查到 1 个可继续的报销草稿/)
  assert.match(assistantMessage.content, /RE-202606-010/)
  assert.match(assistantMessage.content, /待确认/)
  assert.doesNotMatch(assistantMessage.content, />0\.00</)
  assert.match(assistantMessage.content, /2026-06-23 10:00/)
  assert.doesNotMatch(assistantMessage.content, /T10:00:00Z/)
  assert.doesNotMatch(assistantMessage.content, /AP-202606-001/)
  assert.match(assistantMessage.content, /下方三个按钮/)
  assert.doesNotMatch(assistantMessage.content, /跳过草稿后再关联申请单/)
  assert.equal(assistantMessage.suggestedActions.length, 3)
  assert.equal(assistantMessage.suggestedActions[0].action_type, 'open_application_detail')
  assert.equal(assistantMessage.suggestedActions[0].label, '查看草稿 RE-202606-010')
  assert.equal(assistantMessage.suggestedActions[1].action_type, CONTINUE_REIMBURSEMENT_DRAFT_ACTION)
  assert.equal(assistantMessage.suggestedActions[1].label, '继续关联草稿 RE-202606-010')
  assert.equal(assistantMessage.suggestedActions[2].action_type, CREATE_STANDALONE_REIMBURSEMENT_DRAFT_ACTION)
  assert.equal(assistantMessage.suggestedActions[2].label, '独立新建报销单')
})

test('reimbursement association gate shows thinking before querying and renders application cards', async () => {
  conversationStarted.value = false
  let queried = 0
  let resolveClaims = null
  const claimsPromise = new Promise((resolve) => {
    resolveClaims = resolve
  })
  const { conversationMessages, flow } = buildFlow({
    fetchExpenseClaimsForAi: async () => {
      queried += 1
      return claimsPromise
    }
  })

  const gatePromise = flow.startAiReimbursementAssociationGate('我要报销')
  await Promise.resolve()

  assert.equal(queried, 0)
  assert.equal(conversationMessages.value[0]?.role, 'user')
  const thinkingMessage = conversationMessages.value[1]
  assert.equal(thinkingMessage.role, 'assistant')
  assert.equal(thinkingMessage.pending, true)
  assert.equal(thinkingMessage.stewardPlan.streamStatus, 'streaming')
  assert.match(thinkingMessage.stewardPlan.thinkingEvents[0].title, /判断用户意图/)

  await new Promise((resolve) => setTimeout(resolve, 360))
  assert.equal(queried, 1)
  const queryMessage = conversationMessages.value[1]
  assert.notEqual(queryMessage, thinkingMessage)
  assert.match(queryMessage.stewardPlan.thinkingEvents[1].title, /检查报销草稿/)
  resolveClaims({
    items: [{
      id: 'app-travel-card',
      claim_no: 'AP-202606-006',
      employee_name: '张小青',
      expense_type: 'travel_application',
      reason: '北京客户现场实施',
      location: '北京',
      status: 'approved',
      start_date: '2026-06-23',
      end_date: '2026-06-25',
      amount: 1650
    }]
  })
  await gatePromise

  const finalMessage = conversationMessages.value[1]
  assert.notEqual(finalMessage, queryMessage)
  assert.equal(finalMessage.pending, false)
  assert.equal(finalMessage.stewardPlan.streamStatus, 'completed')
  assert.match(finalMessage.content, /没有查到可继续的报销草稿/)
  assert.match(finalMessage.content, /ai-document-card-list/)
  assert.match(finalMessage.content, /ai-document-card--application/)
  assert.match(finalMessage.content, /AP-202606-006/)
})

test('reimbursement association gate times out stalled claim query and unlocks fallback actions', async () => {
  conversationStarted.value = false
  const { conversationMessages, flow } = buildFlow({
    associationQueryTimeoutMs: 5,
    fetchExpenseClaimsForAi: async () => new Promise(() => {})
  })

  const gatePromise = flow.startAiReimbursementAssociationGate('发起报销')
  await new Promise((resolve) => setTimeout(resolve, 360))
  await gatePromise

  const assistantMessage = conversationMessages.value.at(-1)
  assert.equal(assistantMessage.pending, false)
  assert.equal(assistantMessage.stewardPlan.streamStatus, 'failed')
  assert.match(assistantMessage.content, /查询可关联申请单.*超时|查询可关联申请单时出现异常/)
  assert.equal(assistantMessage.suggestedActions.at(-1).action_type, 'skip_required_application_link')
})

test('reimbursement association gate matches short username with returned employee email', async () => {
  conversationStarted.value = false
  const { conversationMessages, flow } = buildFlow({
    currentUser: { name: 'caoxiaozhu', username: 'caoxiaozhu' },
    fetchExpenseClaimsForAi: async () => ({
      items: [
        {
          id: 'app-short-owner',
          claim_no: 'AVF9ST8TT',
          employee_id: 'emp-caoxiaozhu',
          employee_name: '曹笑竹',
          employee_email: 'caoxiaozhu@xf.com',
          employee_no: 'E90919',
          expense_type: 'travel_application',
          reason: '参加相关残联会议',
          location: '上海',
          status: 'approved',
          amount: 1200
        }
      ]
    })
  })

  await flow.startAiReimbursementAssociationGate('发起报销')

  const assistantMessage = conversationMessages.value.at(-1)
  assert.match(assistantMessage.content, /AVF9ST8TT/)
  assert.equal(assistantMessage.suggestedActions[0].action_type, 'select_required_application')
  assert.equal(assistantMessage.suggestedActions[0].payload.application_claim_no, 'AVF9ST8TT')
})

test('linked application selection can create reimbursement draft from association gate', async () => {
  conversationStarted.value = false
  const orchestratorCalls = []
  const createJobCalls = []
  const fetchJobCalls = []
  const { aiExpenseDraft, conversationMessages, flow } = buildFlow({
    fetchExpenseClaimsForAi: async () => ({ items: [] }),
    createLinkedReimbursementDraftJobForAi: async (payload) => {
      createJobCalls.push(payload)
      return {
        job_id: 'linked-draft-job-1',
        status: 'queued',
        message: '已创建后台生成任务。'
      }
    },
    fetchLinkedReimbursementDraftJobForAi: async (jobId) => {
      fetchJobCalls.push(jobId)
      return {
        job_id: jobId,
        status: 'succeeded',
        message: '报销草稿已生成。',
        draft_payload: {
          claim_id: 'draft-linked-1',
          claim_no: 'RE-202606-009',
          status: 'draft',
          expense_type: 'travel',
          reason: '北京客户现场实施'
        }
      }
    },
    runOrchestratorForAi: async (payload, options) => {
      orchestratorCalls.push({ payload, options })
      return {
        status: 'succeeded',
        conversation_id: 'conv-linked-draft',
        result: {
          draft_payload: {
            claim_id: 'draft-linked-1',
            claim_no: 'RE-202606-009',
            status: 'draft',
            expense_type: 'travel',
            reason: '北京客户现场实施'
          }
        }
      }
    }
  })

  await flow.linkAiExpenseApplication({
    application_claim_no: 'AP-202606-001',
    application_expense_type: 'travel_application',
    application_reason: '北京客户现场实施',
    application_location: '北京',
    application_business_time: '2026-06-23 至 2026-06-25',
    application_amount_label: '1,650元'
  })

  assert.equal(orchestratorCalls.length, 0)
  assert.equal(createJobCalls.length, 1)
  assert.equal(createJobCalls[0].context_json.review_action, 'save_draft')
  assert.equal(createJobCalls[0].context_json.expense_scene_selection.application_claim_no, 'AP-202606-001')
  assert.equal(createJobCalls[0].context_json.review_form_values.application_claim_no, 'AP-202606-001')
  assert.deepEqual(fetchJobCalls, ['linked-draft-job-1'])
  assert.equal(aiExpenseDraft.value, null)
  assert.match(conversationMessages.value.at(-1).content, /报销草稿 RE-202606-009 已生成/)
  assert.equal(conversationMessages.value.at(-1).draftPayload.claim_no, 'RE-202606-009')
  assert.equal(conversationMessages.value.at(-1).suggestedActions[0].action_type, 'open_application_detail')
})

test('linked reimbursement draft job resumes from pending history message', async () => {
  conversationStarted.value = true
  const fetchJobCalls = []
  const { conversationMessages, flow } = buildFlow({
    fetchLinkedReimbursementDraftJobForAi: async (jobId) => {
      fetchJobCalls.push(jobId)
      return {
        job_id: jobId,
        status: 'succeeded',
        message: '报销草稿已生成。',
        draft_payload: {
          claim_id: 'draft-resumed-1',
          claim_no: 'RE-202606-011',
          status: 'draft',
          expense_type: 'travel'
        }
      }
    }
  })
  conversationMessages.value.push(createInlineMessage('assistant', '已关联申请单 AP-202606-001，正在后台生成报销草稿...', {
    id: 'pending-linked-draft-message',
    pending: true,
    linkedReimbursementDraftJob: {
      jobId: 'linked-draft-job-resume',
      status: 'queued',
      applicationClaimNo: 'AP-202606-001'
    }
  }))

  flow.resumePendingLinkedReimbursementDraftJobs()
  await new Promise((resolve) => setTimeout(resolve, 5))

  assert.deepEqual(fetchJobCalls, ['linked-draft-job-resume'])
  assert.match(conversationMessages.value.at(-1).content, /报销草稿 RE-202606-011 已生成/)
  assert.match(conversationMessages.value.at(-1).content, /AP-202606-001/)
  assert.equal(conversationMessages.value.at(-1).pending, false)
  assert.equal(conversationMessages.value.at(-1).draftPayload.claim_no, 'RE-202606-011')
})

test('continuing an existing reimbursement draft prompts for attachments or description', () => {
  conversationStarted.value = false
  const { conversationMessages, flow } = buildFlow()

  flow.promptAiReimbursementDraftContinuation({
    claim_id: 'draft-travel-1',
    claim_no: 'RE-202606-010',
    original_message: '我要报销'
  })

  assert.equal(conversationMessages.value.at(-2).role, 'user')
  assert.equal(conversationMessages.value.at(-2).content, '继续关联草稿 RE-202606-010')
  const assistantMessage = conversationMessages.value.at(-1)
  assert.equal(assistantMessage.role, 'assistant')
  assert.match(assistantMessage.content, /请上传相关的附件/)
  assert.match(assistantMessage.content, /补充说明/)
  assert.equal(assistantMessage.suggestedActions[0].action_type, 'open_application_detail')
  assert.equal(assistantMessage.suggestedActions[0].label, '查看草稿 RE-202606-010')
})

test('standalone reimbursement draft branch asks before creating a new draft', () => {
  conversationStarted.value = false
  const { conversationMessages, flow } = buildFlow()

  flow.promptStandaloneReimbursementDraftCreation('我要报销', '独立新建报销单')

  assert.equal(conversationMessages.value.at(-2).role, 'user')
  assert.equal(conversationMessages.value.at(-2).content, '独立新建报销单')
  const assistantMessage = conversationMessages.value.at(-1)
  assert.equal(assistantMessage.role, 'assistant')
  assert.match(assistantMessage.content, /是否新建草稿单据/)
  assert.equal(assistantMessage.suggestedActions[0].label, '新建草稿单据')
  assert.equal(assistantMessage.suggestedActions[0].action_type, 'skip_required_application_link')
  assert.equal(assistantMessage.suggestedActions[1].label, '暂不新建')
})

test('personal workbench routes reimbursement creation intent to association gate before steward', () => {
  assert.match(
    personalWorkbenchAiMode,
    /import\s*\{\s*isReimbursementCreationIntent\s*\}\s*from '\.\/workbenchAiApplicationGateModel\.js'/
  )
  const startConversationIndex = personalWorkbenchAiMode.indexOf('function startInlineConversation')
  const gateIndex = personalWorkbenchAiMode.indexOf('expenseFlow.startAiReimbursementAssociationGate(cleanPrompt', startConversationIndex)
  const stewardIndex = personalWorkbenchAiMode.indexOf('stewardFlow.requestInlineAssistantReply(cleanPrompt', startConversationIndex)

  assert.ok(startConversationIndex >= 0)
  assert.ok(gateIndex > startConversationIndex)
  assert.ok(stewardIndex > gateIndex)
  assert.match(personalWorkbenchAiMode, /function runAiModeAction\(item\)[\s\S]*expenseFlow\.startAiReimbursementAssociationGate\(item\.prompt, item\.label\)/)
})
