// Strenge Preisbrücke (V2-Spezifikation R-06, Befund F-01).
//
// Die Brücke schloss über einen Restposten „Übrige": sie buchte die Memo-Zeile
// „Enthaltene Verpackung und Transport" als Schritt ein, ließ einen echten
// additiven Block aus, und der Restposten kompensierte exakt beide Fehler. Im
// Anlassfall waren das −1,055 = −(0,295 + 0,76) — ein Betrag, der in einer
// Verhandlung nicht vertretbar ist.
//
// Zwei Zusicherungen: Schritte kommen ausschließlich aus additiven Zeilen, und
// es gibt keinen Ausgleichsposten. Ein Rest, der bleibt, wird ausgewiesen statt
// eingebucht.
//
// Werte synthetisch, Struktur wie im Template.

import { describe, it, expect } from 'vitest'
import { buildBridge, type SummaryDiffRowData } from '../summary-view'
import { additiveRowSpecs } from '../summary-row-registry'

function row(metric_key: string, alt: number | null, neu: number | null): SummaryDiffRowData {
  return {
    metric_key,
    alt_value: alt,
    neu_value: neu,
    delta_absolute: alt !== null && neu !== null ? neu - alt : null,
    delta_percent: null,
    currency: 'EUR',
    status: 'anstieg',
    source_alt: null,
    source_neu: null,
  }
}

/**
 * Preisblock, dessen additive Zeilen die Preisdifferenz vollständig erklären:
 * Material +2, Fertigung +1, JIS −0,76 ⇒ Angebotspreis +2,24.
 * Die Memo-Zeile bewegt sich um +0,295 und darf die Brücke NICHT berühren —
 * genau die Konstellation aus dem Anlassfall.
 */
const CLOSING_BLOCK: SummaryDiffRowData[] = [
  row('quotationPrice', 100, 102.24),
  row('materialCosts', 60, 62),
  row('manufacturingCosts', 10, 11),
  row('customsSupplierToBMW', 0.76, null), // Block 7 entfällt
  row('packagingTransportIncluded', 0.54, 0.835), // Memo — nicht additiv
]

describe('buildBridge — strenge Brücke (R-06)', () => {
  it('führt keinen Ausgleichsposten mehr, wenn die additiven Zeilen aufgehen', () => {
    const b = buildBridge(CLOSING_BLOCK)!
    expect(b.steps.some((s) => s.label === 'Übrige')).toBe(false)
    expect(b.steps.some((s) => s.bucket === 'Other' && s.label === 'Übrige')).toBe(false)
  })

  it('bucht die Memo-Zeile nicht als Schritt ein (F-01)', () => {
    const b = buildBridge(CLOSING_BLOCK)!
    const labels = b.steps.map((s) => s.label)
    expect(labels).not.toContain('Enthaltene Verpackung und Transport')
    expect(labels).not.toContain('Enthaltene Zölle')
  })

  it('führt den entfallenen additiven Block als Schritt (F-01, andere Hälfte)', () => {
    const b = buildBridge(CLOSING_BLOCK)!
    const block7 = b.steps.find((s) => s.delta === -0.76)
    expect(block7).toBeDefined()
  })

  it('schließt: Summe der Schritte gleich Preisdifferenz, Rest innerhalb Toleranz', () => {
    const b = buildBridge(CLOSING_BLOCK)!
    const sum = b.steps.reduce((s, x) => s + x.delta, 0)
    expect(sum).toBeCloseTo(b.delta, 6)
    expect(b.residual).toBeCloseTo(0, 6)
    expect(b.residualWithinTolerance).toBe(true)
  })

  it('weist einen unerklärten Rest aus, statt ihn einzubuchen', () => {
    // Angebotspreis steigt um 3, die additiven Zeilen erklären nur 2.
    const b = buildBridge([row('quotationPrice', 100, 103), row('materialCosts', 60, 62)])!

    expect(b.steps.some((s) => s.label === 'Übrige')).toBe(false)
    expect(b.residual).toBeCloseTo(1, 6)
    expect(b.residualWithinTolerance).toBe(false)
    // Der Rest bleibt sichtbar außerhalb der Schritte: die Summe der Schritte
    // ist bewusst NICHT gleich der Preisdifferenz.
    expect(b.steps.reduce((s, x) => s + x.delta, 0)).toBeCloseTo(2, 6)
  })

  it('zieht seine Schritte ausschließlich aus additiven Registry-Zeilen', () => {
    // Jede Metrik bewegt sich; nur die additiven dürfen als Schritt erscheinen.
    const everything: SummaryDiffRowData[] = [
      row('quotationPrice', 100, 110),
      row('materialCosts', 60, 61),
      row('manufacturingCosts', 10, 11),
      row('totalProductionCosts', 70, 72), // Zwischensumme
      row('totalCosts', 75, 78), // Zwischensumme
      row('quotationBasePrice', 90, 95), // Zwischensumme
      row('packagingTransportIncluded', 0.5, 0.9), // Memo
      row('customsIncluded', 0.1, 0.3), // Memo
      row('oneTimeTools', 1000, 2000), // Einmalzahlung
    ]
    const additiveLabels = new Set(additiveRowSpecs().map((s) => s.metricKey))
    const b = buildBridge(everything)!

    expect(b.steps.length).toBe(2) // nur Material und Fertigung bewegen sich additiv
    for (const step of b.steps) {
      expect(step.label).not.toBe('Übrige')
    }
    // Gegenprobe über die Registry statt über eine gepflegte Liste:
    expect([...additiveLabels]).toContain('materialCosts')
    expect([...additiveLabels]).not.toContain('packagingTransportIncluded')
    expect([...additiveLabels]).not.toContain('totalProductionCosts')
  })

  it('bleibt ohne beide Preisseiten null', () => {
    expect(buildBridge([row('materialCosts', 1, 2)])).toBeNull()
    expect(buildBridge([row('quotationPrice', null, 2)])).toBeNull()
  })
})
