import assert from 'node:assert/strict'
import test from 'node:test'

import { mapExpenseClaimToRequest } from '../src/composables/useRequests.js'
import {
  buildApplicationDetailFactItems,
  buildRelatedApplicationFactItems
} from '../src/utils/expenseApplicationDetail.js'

const CREATE_APPLICATION = '\u521b\u5efa\u7533\u8bf7'
const DIRECT_MANAGER_APPROVAL = '\u76f4\u5c5e\u9886\u5bfc\u5ba1\u6279'
const BUDGET_MANAGER_APPROVAL = '\u9884\u7b97\u7ba1\u7406\u8005\u5ba1\u6279'
const FINANCE_APPROVAL = '\u8d22\u52a1\u5ba1\u6279'
const APPROVAL_COMPLETED = '\u5ba1\u6279\u5b8c\u6210'
const APPLICATION_LINK_STATUS = '\u5173\u8054\u5355\u636e\u72b6\u6001'
const APPLICATION_ARCHIVE = '\u7533\u8bf7\u5f52\u6863'
const RETURNED = '\u9000\u56de'
const WAIT_SUBMIT = '\u5f85\u63d0\u4ea4'
const LINKED_APPLICATION = '\u5173\u8054\u5355\u636e'
const PAID = '\u5df2\u4ed8\u6b3e'
const ARCHIVED = '\u5df2\u5f52\u6863'
const WAIT_APPROVAL = '\u7b49\u5f85\u6279\u590d'
const WAIT_BUDGET_ZHAO_APPROVAL = '\u7b49\u5f85 \u8d75\u9884\u7b97 \u6279\u590d'
const WAIT_BUDGET_P8_EXECUTIVE_APPROVAL = '\u7b49\u5f85 P8 Executive \u6279\u590d'
const WAIT_FINANCE_FIONA_APPROVAL = '\u7b49\u5f85 Fiona Finance \u6279\u590d'
const LEADER_RETURNED_STATUS = '\u9886\u5bfc\u5df2\u9000\u56de\uff0c\u5f85\u91cd\u65b0\u63d0\u4ea4'

test('claim mapper exposes employee identifier for reviewer risk profile lookup', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-profile-1',
    claim_no: 'EXP-PROFILE-1',
    employee_id: 'emp-profile-1',
    employee_name: 'Alice',
    department_name: 'Finance',
    expense_type: 'travel',
    reason: 'Trip',
    location: 'Shanghai',
    amount: 1200,
    invoice_count: 1,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T02:00:00.000Z',
    status: 'submitted',
    approval_stage: DIRECT_MANAGER_APPROVAL,
    risk_flags_json: [],
    items: []
  })

  assert.equal(request.employeeId, 'emp-profile-1')
  assert.equal(request.employee_id, 'emp-profile-1')
  assert.equal(request.profileEmployeeId, 'emp-profile-1')
})

test('claim mapper falls back to employee name for legacy profile lookup', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-profile-legacy',
    claim_no: 'EXP-PROFILE-LEGACY',
    employee_name: 'Legacy Alice',
    department_name: 'Finance',
    expense_type: 'travel',
    reason: 'Trip',
    location: 'Shanghai',
    amount: 1200,
    invoice_count: 1,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T02:00:00.000Z',
    status: 'submitted',
    approval_stage: DIRECT_MANAGER_APPROVAL,
    risk_flags_json: [],
    items: []
  })

  assert.equal(request.employeeId, '')
  assert.equal(request.employee_id, '')
  assert.equal(request.profileEmployeeId, 'Legacy Alice')
})

test('claim mapper keeps low reimbursement risk as low risk instead of medium', () => {
  const riskMessage = '票据商品或服务描述较笼统，建议审批人核对真实用途和明细清单。'
  const request = mapExpenseClaimToRequest({
    id: 'claim-low-risk-1',
    claim_no: 'RE-LOW-RISK-1',
    employee_name: 'Alice',
    department_name: 'Finance',
    expense_type: 'travel',
    reason: 'Trip',
    location: 'Shanghai',
    amount: 354,
    invoice_count: 1,
    occurred_at: '2026-02-20T00:00:00.000Z',
    created_at: '2026-06-03T04:22:16.000Z',
    updated_at: '2026-06-03T04:25:48.000Z',
    status: 'draft',
    approval_stage: WAIT_SUBMIT,
    risk_flags_json: [
      {
        source: 'submission_review',
        hit_source: 'rule_center',
        severity: 'low',
        action: 'warning',
        label: '差旅票据服务内容笼统低风险',
        message: riskMessage,
        risk_domain: 'invoice',
        visibility_scope: 'submitter',
        actionability: 'fixable_by_submitter',
        business_stage: 'reimbursement'
      }
    ],
    items: [
      {
        id: 'item-low-risk-train',
        item_date: '2026-02-20',
        item_type: 'train_ticket',
        item_reason: '武汉-上海',
        item_location: '',
        item_amount: 354,
        invoice_id: 'claim-low-risk-1/item-low-risk-train/train.pdf'
      }
    ]
  })

  assert.equal(request.riskTone, 'low')
  assert.equal(request.riskLabel, '低风险')
  assert.equal(request.riskSummary, riskMessage)
  assert.equal(request.expenseItems[0].riskTone, 'low')
  assert.equal(request.expenseItems[0].riskLabel, '低风险')
  assert.equal(request.expenseItems[0].riskText, riskMessage)
})

test('application claims are mapped as application documents', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-1',
    claim_no: 'AP-20260525103045-ABCDEFGH',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: '支撑国网服务器上线部署',
    location: '上海',
    amount: 12000,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T02:00:00.000Z',
    status: 'submitted',
    approval_stage: '直属领导审批',
    risk_flags_json: [],
    items: []
  })

  assert.equal(request.documentTypeCode, 'application')
  assert.equal(request.documentTypeLabel, '申请单')
  assert.equal(request.typeLabel, '差旅费用申请')
  assert.equal(request.secondaryStatusLabel, '申请材料')
  assert.equal(request.secondaryStatusValue, '已进入审批流程')
  assert.equal(request.expenseTableSummary, '预计金额已随申请提交')
  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, '等待 Leader Li 批复', APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.some((step) => step.label === 'AI预审'), false)
  assert.equal(request.progressSteps.some((step) => step.label === '财务审批'), false)
  assert.equal(request.progressSteps.some((step) => step.label === DIRECT_MANAGER_APPROVAL), false)
  assert.equal(request.progressSteps.some((step) => step.label === BUDGET_MANAGER_APPROVAL), false)
  assert.equal(request.progressSteps.find((step) => step.label === '等待 Leader Li 批复')?.rawLabel, DIRECT_MANAGER_APPROVAL)
  assert.equal(request.progressSteps.find((step) => step.label === '等待 Leader Li 批复')?.current, true)
})

test('travel application detail splits trip time into departure and return rows', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-trip-time',
    claim_no: 'AP-20260602103045-TRIPTIME',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: '支撑国网仿生产环境部署',
    location: '上海',
    amount: 3000,
    invoice_count: 0,
    occurred_at: '2026-02-20T00:00:00.000Z',
    submitted_at: '2026-02-20T02:00:00.000Z',
    created_at: '2026-02-20T01:30:00.000Z',
    updated_at: '2026-02-20T02:00:00.000Z',
    status: 'submitted',
    approval_stage: '直属领导审批',
    risk_flags_json: [
      {
        source: 'application_detail',
        application_detail: {
          application_type: '差旅费用申请',
          time: '2026-02-20 至 2026-02-23',
          location: '上海',
          reason: '支撑国网仿生产环境部署',
          days: '4 天',
          transport_mode: '火车',
          amount: '3000'
        }
      }
    ],
    items: []
  })

  const factItems = buildApplicationDetailFactItems(request)
  assert.deepEqual(
    factItems
      .filter((item) => ['trip_start_time', 'trip_return_time'].includes(item.key))
      .map((item) => [item.label, item.value]),
    [
      ['出发时间', '2026-02-20'],
      ['返回时间', '2026-02-23']
    ]
  )
  assert.equal(factItems.some((item) => item.label === '发生时间'), false)
  assert.equal(factItems.some((item) => item.label === '行程时间'), false)
})

test('application claims wait for department P8 budget monitor after leader approval', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-budget',
    claim_no: 'AP-20260525103145-BUDGET',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: '支撑国网服务器上线部署',
    location: '上海',
    amount: 12000,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T03:00:00.000Z',
    status: 'submitted',
    approval_stage: BUDGET_MANAGER_APPROVAL,
    risk_flags_json: [
      {
        source: 'manual_approval',
        event_type: 'expense_application_approval',
        operator: 'Leader Li',
        previous_approval_stage: DIRECT_MANAGER_APPROVAL,
        next_approval_stage: BUDGET_MANAGER_APPROVAL,
        next_approver_name: '赵预算',
        next_approver_grade: 'P8',
        created_at: '2026-05-25T03:00:00.000Z'
      }
    ],
    items: []
  })

  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, WAIT_BUDGET_ZHAO_APPROVAL, APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.find((step) => step.label === WAIT_BUDGET_ZHAO_APPROVAL)?.current, true)
  assert.equal(request.progressSteps.find((step) => step.label === DIRECT_MANAGER_APPROVAL)?.time, 'Leader Li通过')
})

test('application budget wait label uses claim-level budget approver snapshot', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-budget-snapshot',
    claim_no: 'AP-20260525103145-BUDGET-SNAPSHOT',
    employee_name: 'Applicant Zhang',
    department_name: 'Engineering',
    manager_name: 'Leader Li',
    budget_approver_name: 'P8 Executive',
    budget_approver_grade: 'P8',
    budget_approver_role_code: 'executive',
    expense_type: 'travel_application',
    reason: 'Production deployment support',
    location: 'Beijing',
    amount: 12000,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T03:00:00.000Z',
    status: 'submitted',
    approval_stage: BUDGET_MANAGER_APPROVAL,
    risk_flags_json: [
      {
        source: 'manual_approval',
        event_type: 'expense_application_approval',
        operator: 'Leader Li',
        previous_approval_stage: DIRECT_MANAGER_APPROVAL,
        next_approval_stage: BUDGET_MANAGER_APPROVAL,
        created_at: '2026-05-25T03:00:00.000Z'
      }
    ],
    items: []
  })

  assert.equal(request.budgetApproverName, 'P8 Executive')
  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, WAIT_BUDGET_P8_EXECUTIVE_APPROVAL, APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.find((step) => step.label === WAIT_BUDGET_P8_EXECUTIVE_APPROVAL)?.current, true)
})

test('returned application claims include leader return node and supplement status', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-returned',
    claim_no: 'APP-20260525-RETURNED',
    employee_name: 'Applicant Zhang',
    department_name: 'Delivery',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: 'Project onsite support',
    location: 'Shanghai',
    amount: 12000,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: null,
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T04:00:00.000Z',
    status: 'returned',
    approval_stage: WAIT_SUBMIT,
    risk_flags_json: [
      {
        source: 'manual_return',
        event_type: 'expense_application_return',
        operator: 'Leader Li',
        opinion: 'Need clearer budget explanation.',
        return_stage_key: 'direct_manager',
        next_status: 'returned',
        next_approval_stage: WAIT_SUBMIT,
        return_count: 2,
        created_at: '2026-05-25T04:00:00.000Z'
      }
    ],
    items: []
  })

  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, RETURNED, WAIT_SUBMIT]
  )
  assert.equal(request.progressSteps.find((step) => step.label === RETURNED)?.time, 'Leader Li\u9000\u56de')
  assert.match(request.progressSteps.find((step) => step.label === RETURNED)?.detail, /2026-05-25/)
  assert.equal(request.progressSteps.find((step) => step.label === WAIT_SUBMIT)?.current, true)
  assert.equal(request.secondaryStatusValue, LEADER_RETURNED_STATUS)
  assert.equal(request.secondaryStatusTone, 'warning')
  assert.equal(request.progressSteps.some((step) => step.label === APPROVAL_COMPLETED), false)
})

test('approved application claims complete after budget approval', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-approved',
    claim_no: 'AP-20260525113045-HGFEDCBA',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: '李经理',
    expense_type: 'travel_application',
    reason: '支撑国网服务器上线部署',
    location: '上海',
    amount: 12000,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T03:00:00.000Z',
    status: 'approved',
    approval_stage: '审批完成',
    risk_flags_json: [
      {
        source: 'manual_approval',
        event_type: 'expense_application_approval',
        operator: '李经理',
        previous_approval_stage: '直属领导审批',
        next_approval_stage: '预算管理者审批',
        next_approver_name: '赵预算',
        next_approver_grade: 'P8',
        created_at: '2026-05-25T03:00:00.000Z'
      },
      {
        source: 'budget_approval',
        event_type: 'expense_application_budget_approval',
        operator: '赵预算',
        previous_approval_stage: '预算管理者审批',
        next_approval_stage: '审批完成',
        created_at: '2026-05-25T03:00:00.000Z'
      }
    ],
    items: []
  })

  assert.equal(request.documentTypeCode, 'application')
  assert.equal(request.workflowNode, APPLICATION_LINK_STATUS)
  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, BUDGET_MANAGER_APPROVAL, APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.find((step) => step.label === APPLICATION_LINK_STATUS)?.current, true)
  assert.equal(request.progressSteps.find((step) => step.label === APPLICATION_LINK_STATUS)?.time, '未关联')
  assert.equal(request.progressSteps.find((step) => step.label === ARCHIVED)?.done, false)
  assert.equal(request.progressSteps.find((step) => step.label === DIRECT_MANAGER_APPROVAL)?.time, '李经理通过')
  assert.equal(request.progressSteps.find((step) => step.label === BUDGET_MANAGER_APPROVAL)?.time, '赵预算通过')
})

test('application claims hide budget step when leader approval also covers budget approval', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-merged-budget',
    claim_no: 'APP-20260525-MERGED',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: '李预算经理',
    expense_type: 'travel_application',
    reason: '支撑国网服务器上线部署',
    location: '上海',
    amount: 12000,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T03:00:00.000Z',
    status: 'approved',
    approval_stage: APPLICATION_LINK_STATUS,
    risk_flags_json: [
      {
        source: 'approval_routing',
        event_type: 'expense_application_route_decision',
        requires_budget_review: true,
        route: 'budget_manager',
        budget_result: {
          metrics: {
            after_usage_rate: '99.27',
            claim_amount_ratio: '1.27',
            over_budget_amount: '0.00'
          }
        },
        created_at: '2026-05-25T03:00:00.000Z'
      },
      {
        source: 'manual_approval',
        event_type: 'expense_application_approval',
        operator: '李预算经理',
        previous_approval_stage: DIRECT_MANAGER_APPROVAL,
        next_approval_stage: APPLICATION_LINK_STATUS,
        budget_approval_merged: true,
        route_decision: {
          requires_budget_review: true,
          route: 'budget_manager',
          budget_result: {
            metrics: {
              after_usage_rate: '99.27',
              claim_amount_ratio: '1.27',
              over_budget_amount: '0.00'
            }
          }
        },
        created_at: '2026-05-25T03:00:00.000Z'
      }
    ],
    items: []
  })

  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.find((step) => step.label === APPLICATION_LINK_STATUS)?.current, true)
  assert.equal(request.progressSteps.find((step) => step.label === ARCHIVED)?.done, false)
  assert.equal(request.progressSteps.some((step) => step.label === BUDGET_MANAGER_APPROVAL), false)
  assert.equal(request.progressSteps.find((step) => step.label === DIRECT_MANAGER_APPROVAL)?.time, '李预算经理通过')
})

test('approved application claims hide budget step when dynamic route skipped budget review', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-skipped-budget',
    claim_no: 'APP-20260525-SKIPPED',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: 'Project onsite support',
    location: 'Shanghai',
    amount: 500,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T03:00:00.000Z',
    status: 'approved',
    approval_stage: APPROVAL_COMPLETED,
    risk_flags_json: [
      {
        source: 'approval_routing',
        event_type: 'expense_application_route_decision',
        requires_budget_review: false,
        route: 'approval_done',
        created_at: '2026-05-25T03:00:00.000Z'
      },
      {
        source: 'manual_approval',
        event_type: 'expense_application_approval',
        operator: 'Leader Li',
        previous_approval_stage: DIRECT_MANAGER_APPROVAL,
        next_approval_stage: APPROVAL_COMPLETED,
        route_decision: {
          requires_budget_review: false,
          route: 'approval_done'
        },
        created_at: '2026-05-25T03:00:00.000Z'
      }
    ],
    items: []
  })

  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.find((step) => step.label === APPLICATION_LINK_STATUS)?.current, true)
  assert.equal(request.progressSteps.find((step) => step.label === ARCHIVED)?.done, false)
  assert.equal(request.progressSteps.some((step) => step.label === BUDGET_MANAGER_APPROVAL), false)
  assert.equal(request.progressSteps.find((step) => step.label === DIRECT_MANAGER_APPROVAL)?.time, 'Leader Li通过')
})

test('approved application claims hide stale budget route below 90 percent threshold', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-stale-budget-route',
    claim_no: 'APP-20260525-STALE-BUDGET',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: 'Project onsite support',
    location: 'Shanghai',
    amount: 8500,
    invoice_count: 0,
    occurred_at: '2026-05-25T00:00:00.000Z',
    submitted_at: '2026-05-25T02:00:00.000Z',
    created_at: '2026-05-25T01:30:00.000Z',
    updated_at: '2026-05-25T03:00:00.000Z',
    status: 'approved',
    approval_stage: APPROVAL_COMPLETED,
    risk_flags_json: [
      {
        source: 'approval_routing',
        event_type: 'expense_application_route_decision',
        requires_budget_review: true,
        route: 'budget_manager',
        budget_result: {
          metrics: {
            after_usage_rate: '85.00',
            claim_amount_ratio: '85.00',
            over_budget_amount: '0.00'
          }
        },
        created_at: '2026-05-25T03:00:00.000Z'
      },
      {
        source: 'manual_approval',
        event_type: 'expense_application_approval',
        operator: 'Leader Li',
        previous_approval_stage: DIRECT_MANAGER_APPROVAL,
        next_approval_stage: BUDGET_MANAGER_APPROVAL,
        route_decision: {
          requires_budget_review: true,
          route: 'budget_manager',
          budget_result: {
            metrics: {
              after_usage_rate: '85.00',
              claim_amount_ratio: '85.00',
              over_budget_amount: '0.00'
            }
          }
        },
        created_at: '2026-05-25T03:00:00.000Z'
      }
    ],
    items: []
  })

  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.some((step) => step.label === BUDGET_MANAGER_APPROVAL), false)
})

test('approved application claims show linked reimbursement status before archive', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-linked-draft',
    claim_no: 'AP-202606050001-LINKED',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: 'Project onsite support',
    location: 'Shanghai',
    amount: 500,
    invoice_count: 0,
    occurred_at: '2026-06-05T00:00:00.000Z',
    submitted_at: '2026-06-05T02:00:00.000Z',
    created_at: '2026-06-05T01:30:00.000Z',
    updated_at: '2026-06-05T03:00:00.000Z',
    status: 'approved',
    approval_stage: APPLICATION_LINK_STATUS,
    risk_flags_json: [
      {
        source: 'manual_approval',
        event_type: 'expense_application_approval',
        operator: 'Leader Li',
        previous_approval_stage: DIRECT_MANAGER_APPROVAL,
        next_approval_stage: APPLICATION_LINK_STATUS,
        generated_draft_claim_no: 'RE-202606050001-LINKED',
        created_at: '2026-06-05T03:00:00.000Z'
      }
    ],
    items: []
  })

  const linkStep = request.progressSteps.find((step) => step.label === APPLICATION_LINK_STATUS)
  assert.equal(request.workflowNode, APPLICATION_LINK_STATUS)
  assert.equal(linkStep?.current, true)
  assert.equal(linkStep?.time, '关联中 RE-202606050001-LINKED')
  assert.equal(request.secondaryStatusValue, '关联中 RE-202606050001-LINKED')
  assert.equal(request.progressSteps.find((step) => step.label === ARCHIVED)?.done, false)
})

test('application claims are archived only after linked reimbursement is paid', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-application-archived',
    claim_no: 'AP-202606050001-ARCHIVED',
    employee_name: '张三',
    department_name: '交付部',
    manager_name: 'Leader Li',
    expense_type: 'travel_application',
    reason: 'Project onsite support',
    location: 'Shanghai',
    amount: 500,
    invoice_count: 0,
    occurred_at: '2026-06-05T00:00:00.000Z',
    submitted_at: '2026-06-05T02:00:00.000Z',
    created_at: '2026-06-05T01:30:00.000Z',
    updated_at: '2026-06-07T03:00:00.000Z',
    status: 'approved',
    approval_stage: APPLICATION_ARCHIVE,
    risk_flags_json: [
      {
        source: 'application_archive_sync',
        event_type: 'expense_application_archived_by_reimbursement',
        reimbursement_claim_no: 'RE-202606050001-ARCHIVED',
        created_at: '2026-06-07T03:00:00.000Z'
      }
    ],
    items: []
  })

  assert.equal(request.workflowNode, APPLICATION_ARCHIVE)
  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [CREATE_APPLICATION, DIRECT_MANAGER_APPROVAL, APPLICATION_LINK_STATUS, ARCHIVED]
  )
  assert.equal(request.progressSteps.every((step) => step.done), true)
  assert.equal(request.secondaryStatusValue, '已归档')
})

test('progress steps show approval operator time and current stay duration', () => {
  const originalNow = Date.now
  Date.now = () => new Date('2026-05-20T05:00:00.000Z').getTime()

  try {
    const request = mapExpenseClaimToRequest({
      id: 'claim-1',
      claim_no: 'EXP-202605-001',
      employee_name: '张三',
      department_name: '市场部',
      finance_owner_name: 'Wang Finance Group',
      finance_approver_name: 'Fiona Finance',
      expense_type: 'transport',
      reason: '交通报销',
      location: '上海',
      amount: 88,
      invoice_count: 1,
      occurred_at: '2026-05-20T01:00:00.000Z',
      submitted_at: '2026-05-20T02:00:00.000Z',
      created_at: '2026-05-20T01:30:00.000Z',
      updated_at: '2026-05-20T03:30:00.000Z',
      status: 'submitted',
      approval_stage: FINANCE_APPROVAL,
      risk_flags_json: [
        {
          source: 'manual_approval',
          operator: '李经理',
          previous_approval_stage: DIRECT_MANAGER_APPROVAL,
          next_approval_stage: FINANCE_APPROVAL,
          created_at: '2026-05-20T03:30:00.000Z'
        }
      ],
      items: []
    })

    const leaderStep = request.progressSteps.find((step) => step.label === DIRECT_MANAGER_APPROVAL)
    const financeStep = request.progressSteps.find((step) => step.rawLabel === FINANCE_APPROVAL)
    const firstStep = request.progressSteps[0]

    assert.equal(request.riskSummary, '无')
    assert.equal(firstStep.label, LINKED_APPLICATION)
    assert.equal(leaderStep.time, '李经理通过')
    assert.match(leaderStep.detail, /2026-05-20/)
    assert.match(leaderStep.title, /李经理审批通过/)
    assert.equal(request.progressSteps.some((step) => step.label === 'AI预审'), false)
    assert.equal(request.financeOwnerName, 'Wang Finance Group')
    assert.equal(request.financeApproverName, 'Fiona Finance')
    assert.equal(financeStep.label, WAIT_FINANCE_FIONA_APPROVAL)
    assert.equal(financeStep.rawLabel, FINANCE_APPROVAL)
    assert.equal(financeStep.current, true)
    assert.equal(financeStep.time, '停留 1小时30分钟')
  } finally {
    Date.now = originalNow
  }
})

test('progress steps do not expose approver email when manager name is available', () => {
  const originalNow = Date.now
  Date.now = () => new Date('2026-05-20T05:00:00.000Z').getTime()

  try {
    const request = mapExpenseClaimToRequest({
      id: 'claim-email-operator',
      claim_no: 'EXP-202605-003',
      employee_name: '张三',
      department_name: '市场部',
      manager_name: '李经理',
      expense_type: 'transport',
      reason: '交通报销',
      location: '上海',
      amount: 88,
      invoice_count: 1,
      occurred_at: '2026-05-20T01:00:00.000Z',
      submitted_at: '2026-05-20T02:00:00.000Z',
      created_at: '2026-05-20T01:30:00.000Z',
      updated_at: '2026-05-20T03:30:00.000Z',
      status: 'submitted',
      approval_stage: '财务审批',
      risk_flags_json: [
        {
          source: 'manual_approval',
          operator: 'manager@example.com',
          operator_username: 'manager@example.com',
          previous_approval_stage: '直属领导审批',
          next_approval_stage: '财务审批',
          created_at: '2026-05-20T03:30:00.000Z'
        }
      ],
      items: []
    })

    const leaderStep = request.progressSteps.find((step) => step.label === '直属领导审批')

    assert.equal(leaderStep.time, '李经理通过')
    assert.ok(!leaderStep.title.includes('manager@example.com'))
  } finally {
    Date.now = originalNow
  }
})

test('travel expense items describe departure return and lodging time below the date', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-travel-time-labels',
    claim_no: 'EXP-202605-TRAVEL',
    employee_name: '张三',
    department_name: '市场部',
    expense_type: 'travel',
    reason: '北京客户现场出差',
    location: '北京',
    amount: 1108,
    invoice_count: 3,
    occurred_at: '2026-05-13T01:00:00.000Z',
    created_at: '2026-05-13T01:30:00.000Z',
    updated_at: '2026-05-13T03:30:00.000Z',
    status: 'draft',
    approval_stage: '待提交',
    risk_flags_json: [],
    items: [
      {
        id: 'outbound-train',
        item_type: 'train_ticket',
        item_reason: '广州南-北京南',
        item_location: '北京',
        item_date: '2026-05-13',
        item_amount: 354,
        invoice_id: 'outbound.png'
      },
      {
        id: 'hotel',
        item_type: 'hotel_ticket',
        item_reason: '北京全季酒店',
        item_location: '北京',
        item_date: '2026-05-14',
        item_amount: 100,
        invoice_id: 'hotel.png'
      },
      {
        id: 'return-train',
        item_type: 'train_ticket',
        item_reason: '北京南-广州南',
        item_location: '广州',
        item_date: '2026-05-15',
        item_amount: 354,
        invoice_id: 'return.png'
      },
      {
        id: 'allowance',
        item_type: 'travel_allowance',
        item_reason: '系统自动计算出差补贴',
        item_location: '北京',
        item_date: '2026-05-15',
        item_amount: 300,
        invoice_id: '',
        is_system_generated: true
      }
    ]
  })

  assert.equal(request.expenseItems.find((item) => item.id === 'outbound-train')?.dayLabel, '出发时间')
  assert.equal(request.expenseItems.find((item) => item.id === 'return-train')?.dayLabel, '返回时间')
  assert.equal(request.expenseItems.find((item) => item.id === 'hotel')?.dayLabel, '住宿时间')
  assert.equal(request.expenseItems.find((item) => item.id === 'outbound-train')?.detail, '起始地-目的地')
  assert.equal(request.expenseItems.find((item) => item.id === 'return-train')?.detail, '起始地-目的地')
  assert.equal(request.expenseItems.find((item) => item.id === 'hotel')?.detail, '目的地酒店')
  assert.equal(request.expenseItems.at(-1)?.id, 'allowance')
  assert.equal(request.expenseItems.at(-1)?.dayLabel, '系统自动计算')
})

test('ticket description helper does not show the destination city as detail text', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-ticket-detail-helper',
    claim_no: 'EXP-202605-ROUTE',
    employee_name: '张三',
    department_name: '市场部',
    expense_type: 'travel',
    reason: '上海项目出差',
    location: '上海',
    amount: 520,
    invoice_count: 2,
    occurred_at: '2026-05-13T01:00:00.000Z',
    created_at: '2026-05-13T01:30:00.000Z',
    updated_at: '2026-05-13T03:30:00.000Z',
    status: 'draft',
    approval_stage: '待提交',
    risk_flags_json: [],
    items: [
      {
        id: 'flight',
        item_type: 'flight_ticket',
        item_reason: '广州白云-上海虹桥',
        item_location: '上海',
        item_date: '2026-05-13',
        item_amount: 320,
        invoice_id: 'flight.png'
      },
      {
        id: 'ship',
        item_type: 'ship_ticket',
        item_reason: '上海港-舟山港',
        item_location: '舟山',
        item_date: '2026-05-14',
        item_amount: 200,
        invoice_id: 'ship.png'
      }
    ]
  })

  assert.equal(request.expenseItems.find((item) => item.id === 'flight')?.detail, '起始地-目的地')
  assert.equal(request.expenseItems.find((item) => item.id === 'ship')?.detail, '起始地-目的地')
  assert.equal(request.expenseItems.find((item) => item.id === 'ship')?.name, '轮船票')
})

test('finance approval moves reimbursement to pending payment step', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-finance-completed',
    claim_no: 'EXP-202605-004',
    employee_name: '张三',
    department_name: '市场部',
    expense_type: 'transport',
    reason: '交通报销',
    location: '上海',
    amount: 88,
    invoice_count: 1,
    occurred_at: '2026-05-20T01:00:00.000Z',
    submitted_at: '2026-05-20T02:00:00.000Z',
    created_at: '2026-05-20T01:30:00.000Z',
    updated_at: '2026-05-20T04:00:00.000Z',
    status: 'pending_payment',
    approval_stage: '待付款',
    risk_flags_json: [
      {
        source: 'manual_approval',
        operator: '李经理',
        previous_approval_stage: '直属领导审批',
        next_approval_stage: '财务审批',
        created_at: '2026-05-20T03:00:00.000Z'
      },
      {
        source: 'finance_approval',
        operator: '财务复核',
        previous_approval_stage: '财务审批',
        next_approval_stage: '待付款',
        created_at: '2026-05-20T04:00:00.000Z'
      }
    ],
    items: []
  })

  const financeStep = request.progressSteps.find((step) => step.label === '财务审批')
  const paymentStep = request.progressSteps.find((step) => step.label === '待付款')

  assert.equal(request.riskSummary, '无')
  assert.equal(request.workflowNode, '待付款')
  assert.equal(request.approvalKey, 'pending_payment')
  assert.equal(financeStep.time, '财务复核通过')
  assert.match(financeStep.detail, /2026-05-20/)
  assert.equal(paymentStep.current, true)
  assert.equal(paymentStep.done, false)
})

test('paid reimbursement marks payment progress step as complete', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-finance-paid',
    claim_no: 'EXP-202605-005',
    employee_name: '张三',
    department_name: '市场部',
    expense_type: 'transport',
    reason: '交通报销',
    location: '上海',
    amount: 88,
    invoice_count: 1,
    occurred_at: '2026-05-20T01:00:00.000Z',
    submitted_at: '2026-05-20T02:00:00.000Z',
    created_at: '2026-05-20T01:30:00.000Z',
    updated_at: '2026-05-20T05:00:00.000Z',
    status: 'paid',
    approval_stage: '已付款',
    risk_flags_json: [
      {
        source: 'finance_approval',
        operator: '财务复核',
        previous_approval_stage: '财务审批',
        next_approval_stage: '待付款',
        created_at: '2026-05-20T04:00:00.000Z'
      },
      {
        source: 'payment',
        event_type: 'expense_claim_payment_completed',
        operator: '财务付款',
        previous_approval_stage: '待付款',
        next_approval_stage: '已付款',
        created_at: '2026-05-20T05:00:00.000Z'
      },
      {
        source: 'application_handoff',
        event_type: 'expense_application_to_reimbursement_draft',
        application_claim_id: 'application-1',
        application_claim_no: 'APP-20260520-001',
        application_detail: {
          application_type: '差旅费用申请',
          application_content: '差旅费用申请 / 北京',
          application_reason: '支撑国网仿生产环境部署',
          application_days: '4 天',
          application_location: '北京',
          application_amount: '3000',
          application_time: '2026-05-20 至 2026-05-23',
          application_transport_mode: '高铁',
          application_lodging_daily_cap: '600元/天',
          application_subsidy_daily_cap: '120元/天',
          application_transport_policy: '按真实票据复核',
          application_policy_estimate: '交通按真实票据 + 住宿 2,400元 + 补贴 480元',
          application_rule_name: '差旅标准规则',
          application_rule_version: '2026.05'
        }
      }
    ],
    items: []
  })

  const paymentStep = request.progressSteps.find((step) => step.label === '待付款')
  const paidStep = request.progressSteps.find((step) => step.label === PAID)
  const archivedStep = request.progressSteps.find((step) => step.label === ARCHIVED)
  const linkedStep = request.progressSteps.find((step) => step.label === LINKED_APPLICATION)

  assert.equal(request.workflowNode, '已付款')
  assert.equal(request.approvalStatus, '已付款')
  assert.deepEqual(
    request.progressSteps.map((step) => step.label),
    [LINKED_APPLICATION, '待提交', '直属领导审批', '财务审批', '待付款', PAID, ARCHIVED]
  )
  assert.equal(paymentStep.time, '待付款')
  assert.equal(paidStep.time, '已付款')
  assert.equal(paidStep.done, true)
  assert.equal(archivedStep.time, ARCHIVED)
  assert.equal(archivedStep.done, true)
  assert.equal(linkedStep.time, '已关联 APP-20260520-001')
  assert.equal(request.relatedApplication.claimNo, 'APP-20260520-001')
  assert.equal(request.relatedApplication.reason, '支撑国网仿生产环境部署')
  assert.equal(request.relatedApplication.days, '4 天')
  assert.equal(request.relatedApplication.time, '2026-05-20 至 2026-05-23')
  assert.equal(request.relatedApplication.tripStartDate, '2026-05-20')
  assert.equal(request.relatedApplication.tripEndDate, '2026-05-23')
  assert.equal(request.relatedApplication.transportMode, '高铁')
  assert.equal(request.relatedApplication.lodgingDailyCap, '600元/天')
  assert.equal(request.relatedApplication.subsidyDailyCap, '120元/天')
  assert.equal(request.relatedApplication.transportPolicy, '按真实票据复核')
  assert.equal(request.relatedApplication.policyEstimate, '交通按真实票据 + 住宿 2,400元 + 补贴 480元')
  assert.equal(request.relatedApplication.ruleLabel, '差旅标准规则 / 2026.05')
  assert.equal(request.relatedApplication.amountLabel, '¥3,000')
  assert.deepEqual(
    buildRelatedApplicationFactItems(request).map((item) => [item.label, item.value]),
    [
      ['关联单据单号', 'APP-20260520-001'],
      ['申请内容', '差旅费用申请 / 北京'],
      ['出发时间', '2026-05-20'],
      ['返回时间', '2026-05-23'],
      ['申请天数', '4 天'],
      ['申请事由', '支撑国网仿生产环境部署'],
      ['申请地点', '北京'],
      ['出行方式', '高铁'],
      ['住宿上限/天', '600元/天'],
      ['补贴标准/天', '120元/天'],
      ['交通费用口径', '按真实票据复核'],
      ['规则测算参考', '交通按真实票据 + 住宿 2,400元 + 补贴 480元'],
      ['规则依据', '差旅标准规则 / 2026.05'],
      ['预计金额', '¥3,000']
    ]
  )
})

test('reimbursement detail resolves linked application from guided entry context', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-linked-context',
    claim_no: 'EXP-20260520-009',
    employee_name: '张三',
    department_name: '交付部',
    expense_type: 'travel',
    reason: '支撑国网仿生产环境部署',
    location: '北京',
    amount: 654,
    invoice_count: 1,
    occurred_at: '2026-05-20T01:00:00.000Z',
    created_at: '2026-05-20T01:30:00.000Z',
    updated_at: '2026-05-20T02:00:00.000Z',
    status: 'draft',
    approval_stage: '待提交',
    risk_flags_json: [
      {
        source: 'application_link',
        event_type: 'expense_reimbursement_application_linked',
        review_form_values: {
          application_claim_id: 'application-guided-1',
          application_claim_no: 'AP-202605-001',
          application_reason: '支撑国网仿生产环境部署',
          application_location: '北京',
          application_amount: '3000',
          application_amount_label: '¥3,000',
          application_business_time: '2026-05-20 至 2026-05-23',
          application_days: '4 天',
          application_transport_mode: '高铁',
          application_lodging_daily_cap: '600元/天',
          application_subsidy_daily_cap: '120元/天',
          application_transport_policy: '按真实票据复核',
          application_policy_estimate: '交通按真实票据 + 住宿 2,400元 + 补贴 480元'
        },
        expense_scene_selection: {
          application_claim_no: 'AP-202605-001'
        }
      }
    ],
    items: []
  })

  assert.equal(request.relatedApplication.claimNo, 'AP-202605-001')
  assert.equal(request.relatedApplication.reason, '支撑国网仿生产环境部署')
  assert.equal(request.relatedApplication.location, '北京')
  assert.equal(request.relatedApplication.time, '2026-05-20 至 2026-05-23')
  assert.equal(request.relatedApplication.tripStartDate, '2026-05-20')
  assert.equal(request.relatedApplication.tripEndDate, '2026-05-23')
  assert.equal(request.relatedApplication.days, '4 天')
  assert.equal(request.relatedApplication.transportMode, '高铁')
  assert.equal(request.relatedApplication.lodgingDailyCap, '600元/天')
  assert.equal(request.relatedApplication.subsidyDailyCap, '120元/天')
  assert.equal(request.relatedApplication.policyEstimate, '交通按真实票据 + 住宿 2,400元 + 补贴 480元')
  assert.equal(request.relatedApplication.amountLabel, '¥3,000')
  assert.deepEqual(request.expenseItems, [])
})

test('reimbursement detail hides stale application placeholder and allowance rows before receipts', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-linked-stale-placeholder',
    claim_no: 'EXP-20260520-010',
    employee_name: '张三',
    department_name: '交付部',
    expense_type: 'travel',
    reason: '支撑国网仿生产环境部署',
    location: '上海',
    amount: 3480,
    invoice_count: 0,
    occurred_at: '2026-02-20T00:00:00.000Z',
    created_at: '2026-05-20T01:30:00.000Z',
    updated_at: '2026-05-20T02:00:00.000Z',
    status: 'draft',
    approval_stage: '待提交',
    risk_flags_json: [
      {
        source: 'application_link',
        event_type: 'expense_reimbursement_application_linked',
        review_form_values: {
          application_claim_id: 'application-guided-stale',
          application_claim_no: 'AP-202605-010',
          application_reason: '支撑国网仿生产环境部署',
          application_location: '上海',
          application_amount: '3000',
          application_amount_label: '¥3,000',
          application_business_time: '2026-02-20 至 2026-02-23',
          application_days: '4 天',
          application_transport_mode: '火车'
        }
      }
    ],
    items: [
      {
        id: 'placeholder-travel',
        item_type: 'travel',
        item_reason: '支撑国网仿生产环境部署',
        item_location: '上海',
        item_amount: 3000,
        item_date: '2026-02-20',
        invoice_id: ''
      },
      {
        id: 'stale-allowance',
        item_type: 'travel_allowance',
        item_reason: '系统自动计算出差补贴：上海市，1天，120.00元/天',
        item_location: '直辖市/特区',
        item_amount: 120,
        item_date: '2026-02-20',
        invoice_id: ''
      }
    ]
  })

  assert.equal(request.relatedApplication.claimNo, 'AP-202605-010')
  assert.equal(request.relatedApplication.days, '4 天')
  assert.equal(request.amount, 0)
  assert.deepEqual(request.expenseItems, [])
  assert.equal(request.expenseTableSummary, '暂无费用明细')
})

test('reimbursement detail hides stale allowance when linked application days differ', () => {
  const request = mapExpenseClaimToRequest({
    id: 'claim-linked-stale-allowance',
    claim_no: 'EXP-20260520-011',
    employee_name: '张三',
    department_name: '交付部',
    expense_type: 'travel',
    reason: '支撑国网仿生产环境部署',
    location: '上海',
    amount: 474,
    invoice_count: 1,
    occurred_at: '2026-02-20T00:00:00.000Z',
    created_at: '2026-05-20T01:30:00.000Z',
    updated_at: '2026-05-20T02:00:00.000Z',
    status: 'draft',
    approval_stage: '待提交',
    risk_flags_json: [
      {
        source: 'application_link',
        event_type: 'expense_reimbursement_application_linked',
        application_claim_no: 'AP-202605-011',
        application_detail: {
          application_reason: '支撑国网仿生产环境部署',
          application_location: '上海',
          application_time: '2026-02-20 至 2026-02-23',
          application_days: '4 天',
          application_transport_mode: '火车'
        }
      }
    ],
    items: [
      {
        id: 'outbound-train',
        item_type: 'train_ticket',
        item_reason: '武汉-上海',
        item_location: '上海',
        item_amount: 354,
        item_date: '2026-02-20',
        invoice_id: 'ticket-1.png'
      },
      {
        id: 'stale-allowance',
        item_type: 'travel_allowance',
        item_reason: '系统自动计算出差补贴：上海市，1天，120.00元/天',
        item_location: '直辖市/特区',
        item_amount: 120,
        item_date: '2026-02-20',
        invoice_id: ''
      }
    ]
  })

  assert.equal(request.relatedApplication.days, '4 天')
  assert.deepEqual(request.expenseItems.map((item) => item.id), ['outbound-train'])
  assert.equal(request.amount, 354)
  assert.equal(request.expenseTableSummary, '共 1 条费用明细，已关联 1 张票据')
})

test('current direct manager step shows how long the claim has stayed there', () => {
  const originalNow = Date.now
  Date.now = () => new Date('2026-05-20T05:15:00.000Z').getTime()

  try {
    const request = mapExpenseClaimToRequest({
      id: 'claim-2',
      claim_no: 'EXP-202605-002',
      employee_name: '王五',
      department_name: '市场部',
      manager_name: '李经理',
      expense_type: 'office',
      reason: '办公用品',
      location: '上海',
      amount: 128,
      invoice_count: 1,
      occurred_at: '2026-05-20T01:00:00.000Z',
      submitted_at: '2026-05-20T02:00:00.000Z',
      created_at: '2026-05-20T01:30:00.000Z',
      updated_at: '2026-05-20T02:00:00.000Z',
      status: 'submitted',
      approval_stage: '直属领导审批',
      risk_flags_json: [],
      items: []
    })

    const leaderStep = request.progressSteps.find((step) => step.rawLabel === '直属领导审批')
    const submitStep = request.progressSteps.find((step) => step.label === '待提交')

    assert.equal(submitStep.time, '王五提交')
    assert.match(submitStep.detail, /2026-05-20/)
    assert.equal(leaderStep.label, '等待批复')
    assert.doesNotMatch(leaderStep.label, /李经理/)
    assert.equal(leaderStep.rawLabel, '直属领导审批')
    assert.equal(leaderStep.current, true)
    assert.equal(leaderStep.time, '停留 3小时15分钟')
  } finally {
    Date.now = originalNow
  }
})
