// Validierungs-Sektion (R-14, Befund F-05).

import { describe, it, expect } from 'vitest'
import { buildValidation, type ValidationInput } from '../export-validation'

const TOL = { bridgeEur: 0.0005, reconciliationEur: 0.005 }

const input = (over: Partial<ValidationInput> = {}): ValidationInput => ({
  bridge: { residual: 0, residualWithinTolerance: true },
  movers: { reconciliation: { residual: 0, passed: true } },
  metricsRows: Array.from({ length: 17 }, () => ({ labelVerified: true })),
  expectedMetricsRowCount: 17,
  contentHashPresent: true,
  tolerances: TOL,
  ...over,
})

const byId = (r: ReturnType<typeof buildValidation>, id: string) => r.checks.find((c) => c.checkId === id)!

describe('buildValidation (R-14)', () => {
  it('meldet einen sauberen Lauf als bestanden', () => {
    const r = buildValidation(input())
    expect(r.passed).toBe(true)
    expect(r.counts.fail).toBe(0)
    expect(r.checks).toHaveLength(5)
  })

  it('lässt einen offenen Brücken-Rest durchfallen und nennt Messwert und Grenze', () => {
    // Genau das, was V1 verschluckte: der Rest wird zum benannten Befund.
    const r = buildValidation(input({ bridge: { residual: -1.055, residualWithinTolerance: false } }))
    const c = byId(r, 'bridge_closes')

    expect(c.status).toBe('fail')
    expect(c.measured).toBeCloseTo(1.055, 6)
    expect(c.limit).toBe(TOL.bridgeEur)
    expect(r.passed).toBe(false)
  })

  it('lässt eine Treiber-Liste durchfallen, die ihre Quellzeile nicht erklärt', () => {
    const r = buildValidation(input({ movers: { reconciliation: { residual: -0.89, passed: false } } }))
    const c = byId(r, 'movers_reconcile')

    expect(c.status).toBe('fail')
    expect(c.measured).toBeCloseTo(0.89, 6)
  })

  it('lässt eine unvollständige Zeilenmenge durchfallen', () => {
    const r = buildValidation(input({ metricsRows: Array.from({ length: 13 }, () => ({ labelVerified: true })) }))
    const c = byId(r, 'metrics_row_set_complete')

    expect(c.status).toBe('fail')
    expect(c.measured).toBe(13)
    expect(c.limit).toBe(17)
  })

  it('zählt widersprochene Labels und lässt sie durchfallen', () => {
    const rows = [{ labelVerified: true }, { labelVerified: false }, { labelVerified: null }]
    const r = buildValidation(input({ metricsRows: rows, expectedMetricsRowCount: 3 }))
    const c = byId(r, 'registry_labels_verified')

    expect(c.status).toBe('fail')
    expect(c.measured).toBe(1) // nur das echte false zählt
  })

  it('wertet „nicht prüfbar" nicht als Widerspruch', () => {
    // null heisst strukturell nicht prüfbar — daraus darf kein Befund werden.
    const rows = [{ labelVerified: null }, { labelVerified: null }]
    const r = buildValidation(input({ metricsRows: rows, expectedMetricsRowCount: 2 }))

    expect(byId(r, 'registry_labels_verified').status).toBe('pass')
  })

  it('meldet einen fehlenden Inhalts-Hash als Mangel', () => {
    const r = buildValidation(input({ contentHashPresent: false }))
    expect(byId(r, 'determinism_hash_present').status).toBe('fail')
  })

  it('sagt „nicht anwendbar" statt „bestanden", wo die Grundlage fehlt', () => {
    // Ein Check, der mangels Daten immer bestanden meldet, erzeugt Vertrauen,
    // das er nicht deckt.
    const r = buildValidation(input({ bridge: null, movers: null, metricsRows: null }))

    expect(byId(r, 'bridge_closes').status).toBe('not_applicable')
    expect(byId(r, 'movers_reconcile').status).toBe('not_applicable')
    expect(byId(r, 'metrics_row_set_complete').status).toBe('not_applicable')
    expect(r.passed).toBe(true) // nicht anwendbar ist kein Fehler
    expect(r.counts.not_applicable).toBeGreaterThanOrEqual(3)
  })

  it('behandelt eine ungeprüfte Rekonsiliation als nicht anwendbar', () => {
    const r = buildValidation(input({ movers: { reconciliation: { residual: null, passed: null } } }))
    expect(byId(r, 'movers_reconcile').status).toBe('not_applicable')
  })
})
