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

import {
  labelAgentAssetReleaseObservation,
  normalizeAgentAssetReleaseReviewLabel,
  normalizeAgentAssetReleaseReviewQueue
} from '../src/services/agentAssets.js'
import {
  buildReleaseMonitorMetricCards,
  buildReleaseReviewDocumentRoute
} from '../src/views/scripts/useAuditReleaseMonitor.js'

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

const panel = source('../src/components/audit/AuditReleaseMonitorPanel.vue')
const detail = source('../src/components/audit/AuditJsonRiskRuleDetail.vue')
const view = source('../src/views/AuditView.vue')
const service = source('../src/services/agentAssets.js')
const composable = source('../src/views/scripts/useAuditReleaseMonitor.js')

test('盲审面板正负样本混排且不读取或暗示模型预测', () => {
  assert.match(panel, /正负样本随机混排/u)
  assert.match(panel, /候选与基线结论对复核人隐藏/u)
  assert.match(panel, /模型结论已隐藏/u)
  assert.match(panel, /存在真实风险/u)
  assert.match(panel, /确认无该风险/u)
  assert.doesNotMatch(panel, /candidate_hit|baseline_hit/u)
  assert.doesNotMatch(service, /candidate_hit|baseline_hit/u)
  assert.doesNotMatch(panel, /'confirmed'|'false_positive'/u)
  assert.match(panel, /'risk_present'/u)
  assert.match(panel, /'risk_absent'/u)
  assert.doesNotMatch(panel, /确认命中|标记误报|待复核正例/u)
  assert.match(panel, /class="review-decision"[\s\S]*class="review-decision"/u)
  assert.match(panel, /:key="item\.sample_id \|\| item\.observation_id"/u)
  assert.match(panel, /target="_blank"/u)
  assert.match(panel, /rel="noopener noreferrer"/u)
})

test('复核队列只保留盲审字段并可从 source_document_id 打开单据', () => {
  const queue = normalizeAgentAssetReleaseReviewQueue({
    asset_id: 'asset-1',
    release_id: 'release-1',
    stage: 'shadow',
    version: 'v2',
    pending_total: 1,
    telemetry_status: 'collecting',
    items: [{
      sample_id: 'sample-1',
      observation_id: 'observation-1',
      source_document_id: 'claim-1',
      rule_code: 'RISK-001',
      business_stage: 'reimbursement',
      prediction_blinded: true,
      reviewer_count: 1,
      required_reviewers: 2,
      conflicted: false,
      created_at: '2026-07-17T00:00:00Z',
      candidate_hit: true,
      baseline_hit: false
    }]
  })

  assert.deepEqual(Object.keys(queue.items[0]).sort(), [
    'business_stage', 'conflicted', 'created_at', 'observation_id', 'prediction_blinded',
    'required_reviewers', 'reviewer_count', 'rule_code', 'sample_id', 'source_document_id'
  ])
  assert.equal(queue.items[0].prediction_blinded, true)
  assert.deepEqual(buildReleaseReviewDocumentRoute(queue.items[0]), {
    name: 'app-document-detail',
    params: { requestId: 'claim-1' }
  })
  assert.equal(buildReleaseReviewDocumentRoute({ source_document_id: '' }), null)
})

test('负样本证据未就绪时召回和实际漏检不可用，null 不伪装为 0', () => {
  const queue = normalizeAgentAssetReleaseReviewQueue({
    pending_total: 4,
    metrics: {
      observed_count: 20,
      runtime_failure_count: 1,
      runtime_failure_rate: 0.05,
      negative_sample_count: 8,
      negative_labeled_count: 3,
      negative_pending_label_count: 5,
      false_negative_count: null,
      estimated_false_negative_count: null,
      false_negative_upper_bound: null,
      random_negative_population_count: 12,
      random_negative_sample_count: 4,
      random_negative_labeled_count: 1,
      recall: null,
      recall_lower_bound: null,
      recall_confidence_level: 0.95,
      recall_method: 'stratified_random_audit_wilson_upper_bound',
      negative_ground_truth_status: 'insufficient_random_negative_reviews'
    }
  })
  const cards = cardsByLabel(buildReleaseMonitorMetricCards(queue))

  assert.equal(queue.metrics.false_negative_count, null)
  assert.equal(cards['实际漏检'].value, '不可用')
  assert.match(cards['实际漏检'].hint, /不按 0 展示/u)
  assert.equal(cards['估计漏检'].value, '不可用')
  assert.equal(cards['召回率'].value, '不可用')
  assert.equal(cards['召回率'].hint, '随机负样本复核不足')
  assert.equal(cards['负样本积压'].value, '5')
  assert.equal(cards['负样本标注进度'].value, '37.5%')
  assert.equal(cards['随机负样本抽检'].value, '25.0%')
})

test('真实负样本证据显示召回点估计、保守下界和漏检上界', () => {
  const cards = cardsByLabel(buildReleaseMonitorMetricCards({
    pending_total: 2,
    metrics: {
      observed_count: 100,
      runtime_failure_count: 0,
      runtime_failure_rate: 0,
      negative_sample_count: 20,
      negative_labeled_count: 18,
      negative_pending_label_count: 2,
      false_negative_count: 2,
      estimated_false_negative_count: 2.5,
      false_negative_upper_bound: 4.75,
      random_negative_population_count: 40,
      random_negative_sample_count: 10,
      random_negative_labeled_count: 10,
      recall: 0.8,
      recall_lower_bound: 0.64,
      recall_confidence_level: 0.95,
      recall_method: 'stratified_random_audit_wilson_upper_bound',
      negative_ground_truth_status: 'available_stratified_random_audit'
    }
  }))

  assert.equal(cards['实际漏检'].value, '2')
  assert.equal(cards['估计漏检'].value, '2.5')
  assert.equal(cards['估计漏检'].hint, '保守上界 4.75')
  assert.equal(cards['负样本标注进度'].value, '90.0%')
  assert.equal(cards['负样本积压'].value, '2')
  assert.equal(cards['召回率'].value, '80.0%')
  assert.match(cards['召回率'].hint, /保守下界 64\.0% · 95% 置信度/u)
  assert.match(cards['召回率'].hint, /分层随机盲审 Wilson 保守上界/u)
})

test('发布复核使用独立租户鉴权 API、幂等请求号和中性结果文案', () => {
  assert.match(service, /\/release\/review-queue/u)
  assert.match(service, /\/release\/review-queue\/\$\{observationId\}\/labels/u)
  assert.match(service, /normalizeAgentAssetReleaseReviewQueue\(payload\)/u)
  assert.match(composable, /requestId:\s*requestId\(assetId, observationId, label\)/u)
  assert.match(composable, /Promise\.all\(\[/u)
  assert.match(composable, /fetchAgentAssetReleaseState/u)
  assert.match(composable, /fetchAgentAssetReleaseReviewQueue/u)
  assert.match(composable, /已提交“存在真实风险”/u)
  assert.match(composable, /已提交“确认无该风险”/u)
  assert.equal(normalizeAgentAssetReleaseReviewLabel('risk_present'), 'risk_present')
  assert.equal(normalizeAgentAssetReleaseReviewLabel('risk_absent'), 'risk_absent')
  assert.throws(() => normalizeAgentAssetReleaseReviewLabel('confirmed'), /risk_present/u)
  assert.throws(() => normalizeAgentAssetReleaseReviewLabel('false_positive'), /risk_absent/u)
})

test('复核服务只发送 risk_present 或 risk_absent 新语义', async () => {
  const originalFetch = globalThis.fetch
  const bodies = []
  globalThis.fetch = async (_url, options) => {
    bodies.push(JSON.parse(options.body))
    return new Response(JSON.stringify({ label: options.body.includes('risk_present') ? 'risk_present' : 'risk_absent' }), {
      status: 200,
      headers: { 'content-type': 'application/json' }
    })
  }

  try {
    await labelAgentAssetReleaseObservation('asset-1', 'observation-1', 'risk_present', {
      actor: 'auditor', requestId: 'blind-review-present'
    })
    await labelAgentAssetReleaseObservation('asset-1', 'observation-2', 'risk_absent', {
      actor: 'auditor', requestId: 'blind-review-absent'
    })
    assert.throws(
      () => labelAgentAssetReleaseObservation('asset-1', 'observation-3', 'confirmed'),
      /risk_present/u
    )
  } finally {
    globalThis.fetch = originalFetch
  }

  assert.deepEqual(bodies, [{ label: 'risk_present' }, { label: 'risk_absent' }])
})

test('规则详情和审计页复用独立发布监控组件', () => {
  assert.match(detail, /<AuditReleaseMonitorPanel/u)
  assert.match(detail, /@label="\(observationId, label\) => emit\('label-release'/u)
  assert.match(view, /:release-queue="releaseQueue"/u)
  assert.match(view, /@label-release="submitReleaseLabel"/u)
})

function cardsByLabel(cards) {
  return Object.fromEntries(cards.map((card) => [card.label, card]))
}
