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

import {
  buildCfoValueSearch,
  buildSavingsOpportunitySearch,
  normalizeAnalyticsValuePayload,
  recordSavingsRealization
} from '../src/services/analyticsValue.js'
import {
  buildBreakdownGroups,
  buildDataQualityRows,
  buildValueKpis,
  classifyCfoDashboardState,
  formatMoneyValues,
  isDashboardStale,
  readValueFiltersFromQuery,
  resolveRangeWindow,
  writeValueFiltersToQuery
} from '../src/views/scripts/cfoValueDashboardModel.js'
import {
  BUDGET_CONFIGURATION_NOTICE,
  buildCfoOpportunitySourceLinks,
  hasValueOpportunityQuery,
  normalizeValueOpportunityId,
  opportunityMatchesValueContext,
  readBudgetConfigurationFocus,
  readValueOpportunityId,
  shouldClearValueOpportunityError,
  writeValueOpportunityToQuery
} from '../src/views/scripts/cfoValueSourceLinks.js'

const overviewView = readSource('../src/views/OverviewView.vue')
const topBarRange = readSource('../src/components/layout/useTopBarOverviewRange.js')
const appShell = readSource('../src/views/AppShellRouteView.vue')
const appShellComposable = readSource('../src/composables/useAppShell.js')
const budgetCenter = readSource('../src/views/BudgetCenterView.vue')
const budgetCenterScript = readSource('../src/views/scripts/BudgetCenterView.js')
const dashboardComponent = readSource('../src/components/dashboard/CfoValueDashboard.vue')
const dashboardComposable = readSource('../src/composables/useCfoValueDashboard.js')
const dashboardModel = readSource('../src/views/scripts/cfoValueDashboardModel.js')
const valueService = readSource('../src/services/analyticsValue.js')
const actionDialog = readSource('../src/components/dashboard/CfoValueActionDialog.vue')
const opportunityDrawer = readSource('../src/components/dashboard/CfoValueOpportunityDrawer.vue')
const trendChart = readSource('../src/components/charts/CfoValueTrendChart.vue')
const dashboardStyles = readSource('../src/assets/styles/components/cfo-value-dashboard.css')

test('经营价值接口把后端 snake_case 契约规范为前端字段且保留币种分账', () => {
  const normalized = normalizeAnalyticsValuePayload({
    source: { data_status: 'complete', opportunity_count: 2 },
    funnel: { realization_rate_by_currency: { CNY: '0.2500', USD: null } },
    data_quality: { pending_confirmation_count: 1 }
  })

  assert.equal(normalized.source.dataStatus, 'complete')
  assert.equal(normalized.source.opportunityCount, 2)
  assert.equal(normalized.funnel.realizationRateByCurrency.CNY, '0.2500')
  assert.equal(normalized.funnel.realizationRateByCurrency.USD, null)
  assert.equal(normalized.dataQuality.pendingConfirmationCount, 1)
})

test('经营价值与机会台账查询只发送真实筛选字段', () => {
  const dashboardSearch = buildCfoValueSearch({
    start: '2026-07-01T00:00:00.000Z',
    end: '2026-07-16T00:00:00.000Z',
    departmentId: 'dept-1',
    projectCode: '',
    valueKind: 'cash'
  })
  const opportunitySearch = buildSavingsOpportunitySearch({
    page: 2,
    pageSize: 12,
    status: 'in_progress',
    ownerId: 'finance-1'
  })

  assert.equal(dashboardSearch.get('department_id'), 'dept-1')
  assert.equal(dashboardSearch.get('value_kind'), 'cash')
  assert.equal(dashboardSearch.has('project_code'), false)
  assert.equal(opportunitySearch.get('page_size'), '12')
  assert.equal(opportunitySearch.get('status'), 'in_progress')
  assert.equal(opportunitySearch.get('owner_id'), 'finance-1')
})

test('看板明确区分 loading permission error empty partial stale 与 ready', () => {
  const complete = dashboardFixture({ dataStatus: 'complete' })
  const partial = dashboardFixture({ dataStatus: 'partial' })
  const empty = dashboardFixture({ dataStatus: 'empty', opportunityCount: 0, realizationCount: 0 })
  const stale = dashboardFixture({ dataStatus: 'complete', freshnessAt: '2026-07-01T00:00:00Z' })

  assert.equal(classifyCfoDashboardState({ loading: true }), 'loading')
  assert.equal(classifyCfoDashboardState({ error: { status: 403 } }), 'permission')
  assert.equal(classifyCfoDashboardState({ error: new Error('offline') }), 'error')
  assert.equal(classifyCfoDashboardState({ dashboard: empty }), 'empty')
  assert.equal(classifyCfoDashboardState({ dashboard: partial }), 'partial')
  assert.equal(classifyCfoDashboardState({ dashboard: dashboardFixture({ dataStatus: 'complete', freshnessAt: '' }) }), 'partial')
  assert.equal(classifyCfoDashboardState({ dashboard: complete, now: new Date('2026-07-16T01:00:00Z') }), 'ready')
  assert.equal(classifyCfoDashboardState({ dashboard: stale, now: new Date('2026-07-16T01:00:00Z') }), 'stale')
  assert.equal(isDashboardStale(stale, new Date('2026-07-16T01:00:00Z')), true)
})

test('数据质量缺字段显示不可用而不是伪装成零缺口', () => {
  const rows = buildDataQualityRows({ dataQuality: { pendingConfirmationCount: 0 } })
  assert.deepEqual(rows[0], {
    key: 'pendingConfirmationCount',
    label: '待财务确认',
    count: 0,
    displayValue: '0',
    tone: 'ok'
  })
  assert.equal(rows[1].count, null)
  assert.equal(rows[1].displayValue, '—')
  assert.equal(rows[1].tone, 'unavailable')
})

test('零节省、空台账与缺失工时基线不会混为默认数字', () => {
  const dashboard = dashboardFixture({ dataStatus: 'complete', opportunityCount: 2, realizationCount: 1 })
  dashboard.kpis = {
    verifiedCash: { label: '财务确认净现金节省', status: 'empty', values: [], confirmedRealizationCount: 0 },
    releasableLabor: { label: '财务确认可释放工时价值', status: 'collecting', reason: '缺少工时基线', requiredInputs: ['人工分钟'] },
    safeStraightThrough: { label: '安全智能直通率', status: 'collecting', reason: '缺少审计样本', requiredInputs: ['审计结果'] }
  }
  dashboard.funnel = { stages: [{ key: 'estimated', values: [{ currency: 'USD', amount: '50.00' }] }] }

  const [cash, labor, straightThrough] = buildValueKpis(dashboard)
  assert.equal(cash.state, 'zero')
  assert.match(cash.displayValue, /US\$0|\$0/u)
  assert.equal(labor.state, 'baseline-missing')
  assert.equal(labor.displayValue, '待采集')
  assert.equal(straightThrough.displayValue, '待采集')
  assert.match(
    formatMoneyValues([{ currency: 'CNY', amount: '10' }, { currency: 'USD', amount: '20' }]),
    /¥10 \/ (?:US)?\$20/u
  )

  dashboard.filters = { valueKind: 'labor' }
  const [excludedCash] = buildValueKpis(dashboard)
  assert.equal(excludedCash.state, 'unavailable')
  assert.equal(excludedCash.displayValue, '未纳入筛选')
})

test('价值分解按币种拆行，绝不把不同币种相加后比较', () => {
  const groups = buildBreakdownGroups({
    breakdowns: [{
      dimension: 'department',
      items: [{
        dimensionId: 'dept-1',
        dimensionName: '财务部',
        opportunityCount: 2,
        verifiedValues: [
          { currency: 'CNY', amount: '100' },
          { currency: 'USD', amount: '20' }
        ],
        estimatedValues: [
          { currency: 'CNY', amount: '180' },
          { currency: 'USD', amount: '30' }
        ]
      }]
    }]
  })

  assert.equal(groups[0].items.length, 2)
  assert.deepEqual(groups[0].items.map((item) => item.currency), ['CNY', 'USD'])
  assert.deepEqual(groups[0].items.map((item) => item.verifiedMagnitude), [100, 20])
  assert.ok(groups[0].items.every((item) => item.width === '100%'))
})

test('筛选条件和时间窗口可以通过 URL 恢复', () => {
  const filters = readValueFiltersFromQuery({
    value_department_id: 'dept-7',
    value_value_kind: 'cash',
    value_status: 'verified'
  })
  assert.deepEqual(
    { departmentId: filters.departmentId, valueKind: filters.valueKind, status: filters.status },
    { departmentId: 'dept-7', valueKind: 'cash', status: 'verified' }
  )

  const query = writeValueFiltersToQuery({ dashboard: 'value', value_city: 'old' }, {
    ...filters,
    city: ''
  })
  assert.equal(query.dashboard, 'value')
  assert.equal(query.value_department_id, 'dept-7')
  assert.equal(Object.hasOwn(query, 'value_city'), false)

  const range = resolveRangeWindow('custom', { start: '2026-07-01', end: '2026-07-16' })
  assert.match(range.start, /^2026-06-30T16:00:00\.000Z$|^2026-07-01T00:00:00\.000Z$/u)
  assert.ok(new Date(range.end) > new Date(range.start))
})

test('机会抽屉 URL 可恢复、关闭可清理且非法 ID 不会残留', () => {
  const opportunityId = '247b5e9d-f9ee-463f-a88e-6fd41757769b'
  const opened = writeValueOpportunityToQuery({ dashboard: 'value', value_city: '上海' }, opportunityId)
  assert.equal(readValueOpportunityId(opened), opportunityId)
  assert.equal(hasValueOpportunityQuery(opened), true)

  const closed = writeValueOpportunityToQuery(opened)
  assert.equal(hasValueOpportunityQuery(closed), false)
  assert.equal(closed.value_city, '上海')

  const malformed = { ...opened, value_opportunity: '../tenant-b/opportunity' }
  assert.equal(normalizeValueOpportunityId(malformed.value_opportunity), '')
  assert.equal(readValueOpportunityId(malformed), '')
  assert.equal(hasValueOpportunityQuery(writeValueOpportunityToQuery(malformed)), false)
  assert.equal(shouldClearValueOpportunityError({ status: 403 }), true)
  assert.equal(shouldClearValueOpportunityError({ status: 404 }), true)
  assert.equal(shouldClearValueOpportunityError({ status: 500 }), false)
})

test('打开机会必须仍属于当前筛选和时间窗口', () => {
  const opportunity = {
    departmentId: 'ignored-top-level',
    dimensionJson: {
      department_id: 'dept-7',
      project_code: 'PROJECT-1',
      expense_type: 'hotel',
      supplier_id: 'supplier-1',
      city: '上海'
    },
    ownerId: 'finance-1',
    sourceType: 'risk_observation',
    valueKind: 'cash',
    status: 'identified',
    createdAt: '2026-07-15T08:00:00Z'
  }
  const filters = {
    departmentId: 'dept-7', projectCode: 'PROJECT-1', expenseType: 'hotel',
    supplierId: 'supplier-1', city: '上海', ownerId: 'finance-1',
    sourceType: 'risk_observation', valueKind: 'cash', status: 'identified'
  }
  const window = { start: '2026-07-01T00:00:00Z', end: '2026-07-16T23:59:59Z' }

  assert.equal(opportunityMatchesValueContext(opportunity, filters, window), true)
  assert.equal(opportunityMatchesValueContext(opportunity, { ...filters, departmentId: 'dept-other' }, window), false)
  assert.equal(opportunityMatchesValueContext(opportunity, filters, { ...window, end: '2026-07-14T00:00:00Z' }), false)
})

test('来源动作可定位风险单据、预算配置和 CFO 维度且不伪造预算事实', () => {
  const opportunityId = '247b5e9d-f9ee-463f-a88e-6fd41757769b'
  const links = buildCfoOpportunitySourceLinks({
    id: opportunityId,
    claimId: '8a25e85a-b7d8-41dc-8717-57bd1d458fd2',
    claimNoSnapshot: 'BX-20260716-001',
    expenseCaseId: '04452d7a-cdb8-4665-bb9d-99b7f8fc7f1b',
    aiDecisionId: 'b100ca12-8ce3-47eb-a80d-35883410545a',
    sourceType: 'risk_observation',
    sourceId: 'risk-fallback',
    category: 'risk_avoidance',
    ownerId: 'finance-1',
    dimensionJson: {
      department_id: 'dept-7',
      department_name: '财务部',
      project_code: 'PROJECT-1',
      expense_type: 'hotel',
      city: '上海'
    },
    evidence: [{ resourceType: 'risk_observation', resourceId: 'risk-observation-7' }]
  }, {
    dashboardQuery: {
      dashboard: 'value',
      range: '本月',
      value_city: '上海',
      value_opportunity: opportunityId
    }
  })

  const risk = links.find((item) => item.key === 'risk-claim')
  assert.equal(risk.label, '查看风险来源单据')
  assert.equal(risk.to.name, 'app-document-detail')
  assert.equal(risk.to.params.requestId, '8a25e85a-b7d8-41dc-8717-57bd1d458fd2')
  assert.equal(risk.to.query.returnTo, 'value')
  assert.equal(risk.to.query.focus, 'risk')
  assert.equal(risk.to.query.risk_observation_id, 'risk-observation-7')
  assert.equal(risk.to.query.ai_decision_id, 'b100ca12-8ce3-47eb-a80d-35883410545a')
  assert.equal(risk.to.hash, '#risk-observation-active-detail')

  const budget = links.find((item) => item.key === 'budget-configuration')
  assert.equal(budget.label, '查看预算配置视图')
  assert.equal(budget.to.name, 'app-budget')
  assert.equal(budget.to.query.budget_department_id, 'dept-7')
  assert.equal(budget.to.query.budget_expense_type, 'hotel')
  assert.equal(budget.description, BUDGET_CONFIGURATION_NOTICE)
  assert.match(budget.description, /不代表当前节省机会的真实预算金额/u)

  const dimension = links.find((item) => item.key === 'dimension-expenseType')
  assert.equal(dimension.to.name, 'app-overview')
  assert.equal(dimension.to.query.dashboard, 'value')
  assert.equal(dimension.to.query.value_expense_type, 'hotel')
  assert.equal(Object.hasOwn(dimension.to.query, 'value_opportunity'), false)

  assert.deepEqual(readBudgetConfigurationFocus(budget.to.query), {
    active: true,
    departmentId: 'dept-7',
    departmentName: '财务部',
    expenseType: 'hotel'
  })
})

test('带可追溯凭证的实际结果递归序列化为后端 snake_case', async () => {
  const originalFetch = globalThis.fetch
  let requestBody = null
  globalThis.fetch = async (_url, options) => {
    requestBody = JSON.parse(options.body)
    return new Response(JSON.stringify({ realization: {}, opportunity: {}, event: {}, replayed: false }), {
      status: 200,
      headers: { 'content-type': 'application/json' }
    })
  }

  try {
    await recordSavingsRealization('opp-1', {
      requestId: 'request-12345678',
      expectedVersion: 2,
      comment: '平台付款完成',
      actualGross: '100',
      incrementalCost: '10',
      realizedAt: '2026-07-16T00:00:00Z',
      evidenceLevel: 'external_document',
      evidence: [{
        evidenceKey: 'payment-receipt-001',
        evidenceRole: 'payment_receipt',
        resourceType: 'payment_receipt',
        resourceId: 'receipt-001',
        sourceSystem: 'erp',
        contentHash: '0123456789abcdef0123456789abcdef',
        occurredAt: '2026-07-16T00:00:00Z',
        verificationStatus: 'unverified',
        metadataJson: { connector: 'erp' }
      }]
    })
  } finally {
    globalThis.fetch = originalFetch
  }

  assert.equal(requestBody.request_id, 'request-12345678')
  assert.equal(requestBody.expected_version, 2)
  assert.equal(requestBody.incremental_cost, '10')
  assert.equal(requestBody.evidence_level, 'external_document')
  assert.equal(requestBody.evidence[0].evidence_key, 'payment-receipt-001')
  assert.equal(requestBody.evidence[0].metadata_json.connector, 'erp')
})

test('经营价值入口、独立模块、URL 深链与完整状态 UI 已接入分析看板', () => {
  assert.match(topBarRange, /label: '经营价值看板', value: 'value'/u)
  assert.match(overviewView, /<CfoValueDashboard/u)
  assert.match(overviewView, /props\.dashboard === 'value'/u)
  assert.match(appShell, /OVERVIEW_DASHBOARDS = new Set\(\['finance', 'value'/u)
  assert.match(appShell, /dashboard: resolveOverviewDashboard\(overviewDashboard\.value\)/u)
  assert.match(dashboardComposable, /readValueFiltersFromQuery\(route\.query\)/u)
  assert.match(dashboardComposable, /writeValueFiltersToQuery\(route\.query/u)
  assert.match(dashboardComponent, /dashboardState === 'loading'/u)
  assert.match(dashboardComponent, /dashboardState === 'permission'/u)
  assert.match(dashboardComponent, /dashboardState === 'error'/u)
  assert.match(dashboardComponent, /dashboardState === 'empty'/u)
  assert.match(dashboardComponent, /dashboardState === 'partial'/u)
  assert.match(dashboardComponent, /dashboardStale/u)
  assert.match(dashboardComponent, /kpi\.state/u)
  assert.match(dashboardComponent, /机会状态 <em>仅筛选下方台账<\/em>/u)
  assert.match(dashboardModel, /baseline-missing/u)
  assert.match(dashboardModel, /真实零值/u)
  assert.match(dashboardComposable, /valueOpportunityRouteSignature/u)
  assert.match(dashboardComposable, /clearUnavailableOpportunity/u)
  assert.match(dashboardComposable, /router\.replace\(\{ query: nextQuery \}\)/u)
  assert.match(appShellComposable, /DOCUMENT_DETAIL_RETURN_TARGETS = new Set\(\['workbench', 'conversation', 'value'\]\)/u)
  assert.match(appShellComposable, /name: 'app-overview', query: buildValueDashboardReturnQuery\(\)/u)
  assert.match(opportunityDrawer, /id="value-source-links-title">查看来源/u)
  assert.match(opportunityDrawer, /\{\{ link\.label \}\}/u)
  assert.match(budgetCenter, /预算配置视图 · \{\{ budgetConfigurationFocusSummary \}\}/u)
  assert.match(budgetCenter, /isFocusedBudgetCategory\(item\)/u)
  assert.match(budgetCenterScript, /readBudgetConfigurationFocus\(route\.query\)/u)
  assert.match(budgetCenterScript, /\.filter\(\(row\) => matchesBudgetConfigurationExpense\(row\)\)/u)
  assert.match(budgetCenterScript, /这不代表预算为零/u)
})

test('经营价值实现不包含 demo 或 fallback 指标数字', () => {
  assert.doesNotMatch(valueService, /FALLBACK|DEMO|mock/iu)
  assert.doesNotMatch(dashboardComposable, /fallback|demo|mock/iu)
  assert.match(dashboardComponent, /不会展示旧的演示数字/u)
})

test('移动端、键盘与读屏交互具备可验证的生产语义', () => {
  assert.match(dashboardStyles, /@media \(max-width: 760px\)/u)
  assert.match(dashboardStyles, /min-height: 44px/u)
  assert.match(dashboardComponent, /role="alert"/u)
  assert.match(dashboardComponent, /aria-live="polite"/u)
  assert.match(actionDialog, /<label/u)
  assert.match(actionDialog, /role="alert"/u)
  assert.match(actionDialog, /:disabled="submitting"/u)
  assert.match(actionDialog, /requestId: form\.requestId/u)
  assert.match(dashboardComposable, /command\.requestId \|\| createRequestId/u)
  assert.doesNotMatch(dashboardComposable, /recordSavingsRealization/u)
  assert.match(dashboardComposable, /实际结果必须由付款事件或具备可追溯凭证的连接器写入/u)
  assert.match(opportunityDrawer, /action !== 'record_realization'/u)
  assert.match(opportunityDrawer, /当前页面不会提交空证据/u)
  assert.match(opportunityDrawer, /financeConfirmerName/u)
  assert.match(opportunityDrawer, /确认说明：/u)
  assert.doesNotMatch(actionDialog, /实际毛节省/u)
  assert.match(opportunityDrawer, /aria-label="关闭节省机会详情"/u)
  assert.match(trendChart, /role="img"/u)
  assert.match(trendChart, /查看趋势明细表/u)
  assert.match(trendChart, /<caption class="sr-only">/u)
})

function dashboardFixture({
  dataStatus,
  opportunityCount = 1,
  realizationCount = 1,
  freshnessAt = '2026-07-16T00:00:00Z'
}) {
  return {
    source: { dataStatus, opportunityCount, realizationCount, freshnessAt },
    kpis: {},
    funnel: { stages: [] },
    trend: [],
    breakdowns: [],
    guardrails: [],
    dataQuality: {}
  }
}

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