// Rekonsiliation der Treiber-Liste (V2-Spezifikation R-20, Befund F-16).
//
// Im Anlassfall zeigte die Liste drei Prozessschritte mit zusammen +1,95 bei
// einem Fertigungs-Blattdelta von +1,06. Die Differenz von +0,89 war nirgends
// erklärt — vermutlich fehlten die Senkungen, aber nichts sagte das. Wer die
// Summen gegeneinander prüft, findet einen Widerspruch von 84 % und verliert
// damit das Vertrauen in alle übrigen Zahlen.
//
// Die Liste muss deshalb sagen, gegen welche Quelle sie sich rekonsiliert und
// ob sie aufgeht.

import { describe, it, expect } from 'vitest'
import { buildMovers, selectMovers } from '../movers'

const STEP = (stepLabel: string, delta: number | null, uncertain = false) => ({ stepLabel, delta, uncertain })

describe('buildMovers — Rekonsiliation gegen die Quelle (R-20)', () => {
  it('summiert alle Zeilen und weist den Rest gegen die Referenz aus', () => {
    // Drei Schritte erklären +1,95; das Blattdelta ist +1,06.
    const movers = buildMovers({
      rows: [STEP('A', 0.89), STEP('B', 0.55), STEP('C', 0.51)],
      reconcileTo: { label: 'Fertigungskosten', delta: 1.06 },
    })

    expect(movers.totalDelta).toBeCloseTo(1.95, 6)
    expect(movers.reconciliation.reference).toBeCloseTo(1.06, 6)
    expect(movers.reconciliation.residual).toBeCloseTo(-0.89, 6)
    expect(movers.reconciliation.passed).toBe(false)
    expect(movers.reconciliation.label).toBe('Fertigungskosten')
  })

  it('bestätigt eine Liste, die aufgeht', () => {
    const movers = buildMovers({
      rows: [STEP('A', 0.6), STEP('B', -0.2)],
      reconcileTo: { label: 'Fertigungskosten', delta: 0.4 },
    })

    expect(movers.reconciliation.residual).toBeCloseTo(0, 6)
    expect(movers.reconciliation.passed).toBe(true)
  })

  it('sagt „nicht geprüft" statt „bestanden", wenn keine Referenz vorliegt', () => {
    // Ohne Vergleichsgrösse ist die Liste weder richtig noch falsch — und darf
    // sich nicht als geprüft ausgeben.
    const movers = buildMovers({ rows: [STEP('A', 0.6)] })

    expect(movers.reconciliation.reference).toBeNull()
    expect(movers.reconciliation.passed).toBeNull()
    expect(movers.reconciliation.residual).toBeNull()
  })

  it('behandelt eine nicht ermittelbare Referenz wie keine', () => {
    const movers = buildMovers({
      rows: [STEP('A', 0.6)],
      reconcileTo: { label: 'Fertigungskosten', delta: null },
    })

    expect(movers.reconciliation.passed).toBeNull()
  })
})

describe('selectMovers — die Auswahl deklariert sich selbst (R-20)', () => {
  const rows = buildMovers({
    rows: [STEP('A', 3), STEP('B', -2), STEP('C', 1), STEP('D', -0.5), STEP('E', 0.25), STEP('F', 0.1)],
  }).rows

  it('nennt Umfang, gezeigte Summe und Rest', () => {
    const sel = selectMovers(rows, 5)

    expect(sel.shown).toHaveLength(5)
    expect(sel.ofTotal).toBe(6)
    expect(sel.shownSum).toBeCloseTo(3 - 2 + 1 - 0.5 + 0.25, 6)
    expect(sel.residualSum).toBeCloseTo(0.1, 6)
    expect(sel.truncated).toBe(true)
  })

  it('geht bei „alle" ohne Rest auf', () => {
    const sel = selectMovers(rows, 'all')

    expect(sel.ofTotal).toBe(6)
    expect(sel.shown).toHaveLength(6)
    expect(sel.residualSum).toBe(0)
    expect(sel.truncated).toBe(false)
  })

  it('hält die Invariante: Gezeigtes plus Rest ergibt die Gesamtsumme', () => {
    for (const n of [5, 12, 20, 'all'] as const) {
      const sel = selectMovers(rows, n)
      const total = rows.reduce((s, r) => s + r.delta, 0)
      expect(sel.shownSum + sel.residualSum).toBeCloseTo(total, 6)
    }
  })
})
