// Der SUM-Produzent: Summary-Vergleich → Differenzkatalog (Loop 3 Schritt 2).

import { describe, it, expect } from 'vitest'
import {
  differencesFromSummary,
  differenceIdsByMetricKey,
  sortKeyTuple,
} from '../differences-from-summary'
import { buildCatalog } from '../all-differences'
import type { SummaryMetricDiff } from '../summary-metrics'

const sdiff = (over: Partial<SummaryMetricDiff> = {}): SummaryMetricDiff => ({
  metricKey: 'materialCosts',
  altValue: 2.0,
  neuValue: 2.5,
  deltaAbsolute: 0.5,
  deltaPercent: 0.25,
  status: 'anstieg',
  currency: 'EUR',
  sourceAlt: 'D10',
  sourceNeu: 'D10',
  labelFileAlt: null,
  labelFileNeu: null,
  labelVerifiedAlt: null,
  labelVerifiedNeu: null,
  ...over,
})

describe('differencesFromSummary', () => {
  it('lässt unveränderte Kennzahlen aus dem Katalog heraus', () => {
    const r = differencesFromSummary([sdiff({ status: 'konstant', deltaAbsolute: 0 })])
    expect(r.differences).toEqual([])
    expect(r.skippedDe).toEqual([])
  })

  it('übersetzt Zugang, Wegfall und Wertband in die Katalog-Arten', () => {
    const r = differencesFromSummary([
      sdiff({ metricKey: 'materialCosts', status: 'neu', altValue: null, deltaAbsolute: null, sourceAlt: null }),
      sdiff({ metricKey: 'manufacturingCosts', status: 'entfallen', neuValue: null, deltaAbsolute: null, sourceNeu: null }),
      sdiff({ metricKey: 'quotationPrice', status: 'kritisch_50' }),
    ])
    expect(r.differences.map((d) => d.kind)).toEqual(['added', 'removed', 'changed'])
    expect(r.differences.every((d) => d.area === 'SUM')).toBe(true)
  })

  it('führt den Formelbefund orthogonal zum Wertband', () => {
    // Der dritte Formel-Fall behält sein Wertband und trägt den Befund dazu.
    const [d] = differencesFromSummary([
      sdiff({
        status: 'anstieg',
        formulaFinding: { kind: 'formel_geaendert_wert_geaendert', explanation: 'Formel anders, Wert anders.' },
      }),
    ]).differences
    expect(d.kind).toBe('changed')
    expect(d.formulaFindingKind).toBe('formel_geaendert_wert_geaendert')
  })

  it('übersetzt die stille Formeländerung als Differenz mit unverändertem Wert', () => {
    const [d] = differencesFromSummary([
      sdiff({
        status: 'formel_geaendert',
        neuValue: 2.0,
        deltaAbsolute: 0,
        formulaFinding: { kind: 'formel_geaendert_wert_gleich', explanation: 'Formel anders, Wert gleich.' },
      }),
    ]).differences
    expect(d.kind).toBe('changed')
    expect(d.formulaFindingKind).toBe('formel_geaendert_wert_gleich')
    expect(d.delta).toBe(0)
  })

  it('erzeugt keine Zelle ohne vollständige Herkunft', () => {
    // Der Diff kennt die Adresse, aber weder Formel noch Wertzustand — eine
    // behauptete Angabe wäre eine Falschaussage im Provenienzfeld.
    const r = differencesFromSummary([sdiff({})])
    expect(r.differences[0].cells).toEqual([])
  })

  it('nimmt die Ankerzeile aus dem aktuellen Stand, sonst aus dem Vergabestand', () => {
    const r = differencesFromSummary([
      sdiff({ sourceAlt: 'D10', sourceNeu: 'D42' }),
      sdiff({ metricKey: 'quotationPrice', sourceAlt: 'D18', sourceNeu: null, status: 'entfallen', neuValue: null, deltaAbsolute: null }),
    ])
    expect(r.differences.map((d) => d.anchorRow)).toEqual([42, 18])
  })

  it('benennt einen Status ohne Ziel, statt ihn still zu verschlucken', () => {
    // Das Rechen-Trio gehört dem Feld-Vergleich; sollte es je hier ankommen,
    // muss es sichtbar werden.
    const r = differencesFromSummary([sdiff({ status: 'blockiert', deltaAbsolute: null })])
    expect(r.differences).toEqual([])
    expect(r.skippedDe).toHaveLength(1)
    expect(r.skippedDe[0]).toContain('blockiert')
  })

  it('ordnet die Kennungen über die Sortierschlüssel zu — permutationsfest', () => {
    const eingaben = [
      sdiff({ metricKey: 'materialCosts', sourceNeu: 'D10', sourceAlt: 'D10' }),
      sdiff({ metricKey: 'manufacturingCosts', status: 'senkung', deltaAbsolute: -0.3, sourceNeu: 'D14', sourceAlt: 'D14' }),
      sdiff({ metricKey: 'quotationPrice', status: 'anstieg', sourceNeu: 'D42', sourceAlt: 'D42' }),
    ]

    const vorwärts = differencesFromSummary(eingaben)
    const rückwärts = differencesFromSummary([...eingaben].reverse())

    const idsVorwärts = differenceIdsByMetricKey(buildCatalog(vorwärts.differences).records, vorwärts.metricKeyByTuple)
    const idsRückwärts = differenceIdsByMetricKey(buildCatalog(rückwärts.differences).records, rückwärts.metricKeyByTuple)

    expect(idsVorwärts.get('materialCosts')).toEqual(idsRückwärts.get('materialCosts'))
    expect(idsVorwärts.get('quotationPrice')).toEqual(idsRückwärts.get('quotationPrice'))
    // Jede veränderte Kennzahl trägt genau eine Kennung im Vergabeformat.
    for (const ids of idsVorwärts.values()) {
      expect(ids).toHaveLength(1)
      expect(ids[0]).toMatch(/^D-SUM-\d{3,}$/)
    }
  })

  it('trennt zwei Kennzahlen auf derselben Blattzeile über die Beschreibung', () => {
    // Der persistierte Pfad kann für alle Zeilen dieselbe Quellzelle führen —
    // dann trägt nur noch die Beschreibung. Sie enthält das Kennzahl-Label und
    // ist deshalb je Kennzahl verschieden.
    const r = differencesFromSummary([
      sdiff({ metricKey: 'materialCosts', sourceNeu: 'D42', sourceAlt: 'D42' }),
      sdiff({ metricKey: 'manufacturingCosts', sourceNeu: 'D42', sourceAlt: 'D42' }),
    ])
    const ids = differenceIdsByMetricKey(buildCatalog(r.differences).records, r.metricKeyByTuple)
    expect(ids.get('materialCosts')).toHaveLength(1)
    expect(ids.get('manufacturingCosts')).toHaveLength(1)
    expect(ids.get('materialCosts')![0]).not.toBe(ids.get('manufacturingCosts')![0])
  })

  it('sortKeyTuple bildet die vier Schlüssel der Vergabe ab', () => {
    const [raw] = differencesFromSummary([sdiff({})]).differences
    const [record] = buildCatalog([raw]).records
    expect(sortKeyTuple(record)).toBe(sortKeyTuple(raw))
  })
})
