// Loop 10 View-Spec-Adoption, Modul 5: buildSummaryRun — die aus Schale +
// Renderer an die Build-Zeit gezogenen Ableitungen der Zusammenfassungs-
// Sektionen (view-spec-adoption-analyse.md, Abschnitt 2).
//
// Die Engine-Funktionen selbst (buildBridge/buildBuckets/buildMetricsTable/
// buildMovers, …) bleiben über ihre eigenen Tests geprüft — hier wird ihre
// VERDRAHTUNG in den Run geprüft, plus alles, was vorher ungetestet im
// Render-Loop lag (Facts-Zählungen, Kennzahlen-Richtung/-Label,
// Explain-Verdrahtung, Treiber-Label-/Unsicherheits-Auflösung).
//
// FIXTURE-DATEN-REGEL: alle Werte sind freie Erfindung, aus keiner realen
// Mappe entnommen.

import { describe, expect, it } from 'vitest'
import { buildSummaryRun } from '../summary-run'
import type { SummaryDiffRowData } from '../summary-view'
import type { AppendixStepMatch } from '../appendix-run'
import type {
  ManufacturingDiff,
  ManufacturingStepRow,
} from '../manufacturing-run'

const EMPTY_INPUT = {
  summaryDiffs: [] as SummaryDiffRowData[],
  diffs: [] as ManufacturingDiff[],
  steps: [] as ManufacturingStepRow[],
  stepMatches: [] as AppendixStepMatch[],
  plausibility: [],
  partName: null,
  partNumber: 'P0000001',
  baselineFileName: 'alt-fixture.xlsx',
  comparisonFileName: 'neu-fixture.xlsx',
  baselineTemplateProfile: null,
  comparisonTemplateProfile: null,
  engineVersion: { parser: '9.9.9-fixture' },
  createdAt: '2026-08-01T00:00:00Z',
}

function summaryRow(over: Partial<SummaryDiffRowData>): SummaryDiffRowData {
  return {
    metric_key: 'quotationPrice',
    alt_value: null,
    neu_value: null,
    delta_absolute: null,
    delta_percent: null,
    currency: 'EUR',
    status: null,
    source_alt: null,
    source_neu: null,
    ...over,
  }
}

const PRICE_ROW = summaryRow({
  metric_key: 'quotationPrice',
  alt_value: 10,
  neu_value: 12,
  delta_absolute: 2,
  delta_percent: 0.2,
  status: 'anstieg',
  source_alt: 'Zusammenfassung!F9',
  source_neu: 'Zusammenfassung!F9',
})

describe('buildSummaryRun — Grundzustand und Gates', () => {
  it('meldet leere Summary-Daten ehrlich (Export-Gate zu, Platzhalter-Formen)', () => {
    const run = buildSummaryRun(EMPTY_INPUT)
    expect(run.hasSummaryData).toBe(false)
    expect(run.currency).toBeNull()
    expect(run.form.hasData).toBe(false)
    expect(run.buckets.data).toBeNull()
    expect(run.buckets.factsDe).toEqual([])
    expect(run.bridge.data).toBeNull()
    expect(run.projection.factsDe).toEqual([])
    expect(run.metrics.presentCount).toBe(0)
  })

  it('öffnet das Export-Gate mit Daten und reicht die Währung durch', () => {
    const run = buildSummaryRun({ ...EMPTY_INPUT, summaryDiffs: [PRICE_ROW] })
    expect(run.hasSummaryData).toBe(true)
    expect(run.currency).toBe('EUR')
  })
})

describe('buildSummaryRun — Kennzahlen-Zeilen-Specs', () => {
  it('hängt Richtung, Status-Label und Quell-Verweis an die present-Zeile', () => {
    const run = buildSummaryRun({ ...EMPTY_INPUT, summaryDiffs: [PRICE_ROW] })
    expect(run.metrics.presentCount).toBe(1)
    const spec = run.metrics.rows.find((r) => r.row.metricKey === 'quotationPrice')
    expect(spec).toBeDefined()
    expect(spec?.dir).toBe('up')
    expect(spec?.statusLabelDe).toBe('Anstieg ▲')
    // Referenz-Konsistenz: rows[i].row IST die Export-Tabellenzeile.
    expect(run.metrics.table).toContain(spec?.row)
    expect(run.metrics.factsDe[0]).toContain('Kennzahlen verändert')
  })

  it('pinnt die Bestands-Semantik: Richtung NUR aus deltaAbsolute (kein Prozent-Fallback)', () => {
    const run = buildSummaryRun({
      ...EMPTY_INPUT,
      summaryDiffs: [
        summaryRow({
          metric_key: 'manufacturingCosts',
          alt_value: 5,
          neu_value: 4,
          delta_absolute: null,
          delta_percent: -0.3,
          status: 'senkung',
        }),
      ],
    })
    const spec = run.metrics.rows.find(
      (r) => r.row.metricKey === 'manufacturingCosts',
    )
    // Mit diffDirection wäre dir 'down' und das Label trüge „▼" — die
    // Kennzahlen-Tabelle hatte diesen Fallback nie; still vereinheitlichen
    // wäre eine sichtbare Verhaltensänderung (Builder-Kopf).
    expect(spec?.dir).toBe('flat')
    expect(spec?.statusLabelDe).toBe('Senkung')
  })

  it('verdrahtet den Regel-Befund (metric_key, ohne Step-Scope) in die Explain-Attribute', () => {
    const run = buildSummaryRun({
      ...EMPTY_INPUT,
      summaryDiffs: [PRICE_ROW],
      plausibility: [
        {
          issue_type: 'rule_summary_fixture',
          severity: 'pruefen',
          step_label: null,
          field: 'quotationPrice',
          explanation: 'Fixture-Regel-Befund Preis.',
        },
      ],
    })
    const spec = run.metrics.rows.find((r) => r.row.metricKey === 'quotationPrice')
    const attrs = JSON.stringify(spec?.explainAttrs)
    expect(attrs).toContain('Fixture-Regel-Befund Preis.')
    expect(attrs).toContain('alt-fixture.xlsx')
  })
})

describe('buildSummaryRun — Treiber/Movers', () => {
  const STEP_NEU: ManufacturingStepRow = {
    id: 's-neu-1',
    file_id: 'f-neu',
    process_name: 'Station Mover-Fixture',
    position_number: '10',
  }
  const MATCH: AppendixStepMatch = {
    id: 'm-1',
    alt_step_id: null,
    neu_step_id: 's-neu-1',
    match_status: 'candidate_match',
    confidence_score: null,
    match_method: null,
    explanation: null,
    requires_review: false,
  }

  it('baut Prozessschritt-Zeilen mit aufgelöstem Label und Unsicherheits-Flag', () => {
    const run = buildSummaryRun({
      ...EMPTY_INPUT,
      steps: [STEP_NEU],
      stepMatches: [MATCH],
      diffs: [
        {
          step_match_id: 'm-1',
          field: 'fk',
          alt_value: 3,
          neu_value: 5,
          delta_absolute: 2,
          delta_percent: null,
          delta_percentage_points: null,
          status: null,
        },
      ],
    })
    expect(run.movers.data.kind).toBe('prozessschritt')
    expect(run.movers.data.rows[0].label).toBe('Station Mover-Fixture')
    // candidate_match zählt als unsicher (kanonische isUncertainMatch-Regel).
    expect(run.movers.data.rows[0].uncertain).toBe(true)
    expect(run.movers.factsDe[0]).toContain('Treiber')
  })

  it('fällt ohne Prozessschritt-Deltas aufs Vergleichsobjekt zurück (partName vor partNumber)', () => {
    const run = buildSummaryRun({
      ...EMPTY_INPUT,
      partName: 'Fixture-Teil XY',
      summaryDiffs: [PRICE_ROW],
    })
    expect(run.movers.data.kind).toBe('vergleichsobjekt')
    expect(run.movers.data.rows[0].label).toBe('Fixture-Teil XY')
  })
})

describe('buildSummaryRun — Facts-Zählungen', () => {
  it('zählt veränderte Formularpositionen — und meldet sonst „unverändert"', () => {
    const changed = buildSummaryRun({
      ...EMPTY_INPUT,
      summaryDiffs: [PRICE_ROW],
    })
    // quotationPrice ist eine der Formularpositionen: alt≠neu → 1 verändert.
    expect(changed.form.hasData).toBe(true)
    expect(changed.form.factsDe[0]).toContain('Positionen verändert')

    const same = buildSummaryRun({
      ...EMPTY_INPUT,
      summaryDiffs: [
        summaryRow({
          metric_key: 'quotationPrice',
          alt_value: 10,
          neu_value: 10,
          delta_absolute: 0,
          status: 'konstant',
        }),
      ],
    })
    expect(same.form.factsDe).toEqual(['Alle Formularpositionen unverändert'])
  })
})
