// Hebel-Belege (levers-run.ts): Builder-Tests über die ECHTE Kette —
// die Zeilen-Specs kommen aus buildManufacturingRun über Fixture-Rohzeilen,
// nicht aus handgebauten ManufacturingDeltaRowSpec-Objekten. So testet der
// Test dieselbe Verdrahtung wie die Seite (page.tsx baut leversRun aus
// manufacturingRun.deltas.rows).
//
// FIXTURE-DATEN-REGEL: alle Werte sind freie Erfindung, aus keiner realen
// Mappe entnommen.

import { describe, expect, it } from 'vitest'
import { buildLevers } from '../levers'
import { buildLeversRun } from '../levers-run'
import {
  buildManufacturingRun,
  type ManufacturingDiff,
  type ManufacturingStepRow,
} from '../manufacturing-run'
import type { AppendixStepMatch } from '../appendix-run'

const EMPTY_INPUT = {
  diffs: [] as ManufacturingDiff[],
  steps: [] as ManufacturingStepRow[],
  stepMatches: [] as AppendixStepMatch[],
  plausibility: [],
  baselineFileId: 'f-alt' as string | null,
  comparisonFileId: 'f-neu' as string | null,
  baselineSummaryFieldCandidates: [],
  comparisonSummaryFieldCandidates: [],
  baselineFileName: 'alt.xlsx',
  comparisonFileName: 'neu.xlsx',
  baselineManufacturingParseConfidence: null,
  comparisonManufacturingParseConfidence: null,
  baselineTemplateProfile: null,
  comparisonTemplateProfile: null,
  engineVersion: null,
  engineVersionParser: null,
  createdAt: '2026-08-01T00:00:00Z',
}

function step(
  id: string,
  fileId: string,
  name: string,
  sourceCells: Record<string, string> | null,
): ManufacturingStepRow {
  return {
    id,
    file_id: fileId,
    process_name: name,
    position_number: null,
    source_cells: sourceCells,
  }
}

function match(
  id: string,
  altStepId: string | null,
  neuStepId: string | null,
  requiresReview = false,
): AppendixStepMatch {
  return {
    id,
    alt_step_id: altStepId,
    neu_step_id: neuStepId,
    match_status: 'matched',
    confidence_score: null,
    match_method: null,
    explanation: null,
    requires_review: requiresReview,
  } as AppendixStepMatch
}

function diff(
  matchId: string | null,
  field: string,
  altValue: number | null,
  neuValue: number | null,
  deltaPercent: number | null,
): ManufacturingDiff {
  return {
    step_match_id: matchId,
    field,
    alt_value: altValue,
    neu_value: neuValue,
    delta_absolute:
      altValue !== null && neuValue !== null ? neuValue - altValue : null,
    delta_percent: deltaPercent,
    delta_percentage_points: null,
    status: 'anstieg',
  }
}

function rowsFor(input: {
  diffs: ManufacturingDiff[]
  steps: ManufacturingStepRow[]
  stepMatches: AppendixStepMatch[]
}) {
  return buildManufacturingRun({ ...EMPTY_INPUT, ...input }).deltas.rows
}

describe('buildLeversRun', () => {
  it('hängt beide A1-Zellbelege an einen Hebel mit voll aufgelöstem Match', () => {
    const run = buildLeversRun(
      rowsFor({
        steps: [
          step('s-alt', 'f-alt', 'Spritzguss', { zykluszeit: 'Fertigung!C5' }),
          step('s-neu', 'f-neu', 'Spritzguss', { zykluszeit: 'Fertigung!C7' }),
        ],
        stepMatches: [match('m-1', 's-alt', 's-neu')],
        diffs: [diff('m-1', 'zykluszeit', 10, 12, 0.2)],
      }),
    )
    expect(run.levers).toHaveLength(1)
    expect(run.levers[0]).toMatchObject({
      stepLabel: 'Spritzguss',
      kpiLabel: 'Zykluszeit',
      alt: 10,
      neu: 12,
      deltaPercent: 0.2,
      uncertain: false,
      altCell: 'Fertigung!C5',
      neuCell: 'Fertigung!C7',
    })
    expect(run.belowThreshold).toBe(false)
    expect(run.anyUncertain).toBe(false)
  })

  it('behandelt einen Hebel OHNE step_match_id als unsicher (Schale-Parität, nicht rows.uncertain)', () => {
    // Paritäts-Kante: ManufacturingDeltaRowSpec.uncertain ist für Zeilen ohne
    // Match false (Deltas-Tabelle) — die Hebel-Semantik war immer true
    // (ein Hebel ohne zuordenbaren Prozessschritt ist kein belastbares
    // Verhandlungsargument). buildLeversRun muss aus diff.step_match_id
    // rekonstruieren, nicht row.uncertain durchreichen.
    const rows = rowsFor({
      steps: [],
      stepMatches: [],
      diffs: [diff(null, 'mss', 100, 120, 0.2)],
    })
    expect(rows[0].uncertain).toBe(false)
    const run = buildLeversRun(rows)
    expect(run.levers).toHaveLength(1)
    expect(run.levers[0].uncertain).toBe(true)
    expect(run.levers[0].stepLabel).toBe('—')
    expect(run.levers[0].altCell).toBeNull()
    expect(run.levers[0].neuCell).toBeNull()
    expect(run.anyUncertain).toBe(true)
  })

  it('lässt Belege ehrlich null, wenn der gematchte Step keine source_cells trägt (Lauf vor KAR-886)', () => {
    const run = buildLeversRun(
      rowsFor({
        steps: [
          step('s-alt', 'f-alt', 'Montage', null),
          step('s-neu', 'f-neu', 'Montage', null),
        ],
        stepMatches: [match('m-1', 's-alt', 's-neu')],
        diffs: [diff('m-1', 'lohnkosten', 30, 36, 0.2)],
      }),
    )
    expect(run.levers).toHaveLength(1)
    expect(run.levers[0].altCell).toBeNull()
    expect(run.levers[0].neuCell).toBeNull()
  })

  it('reicht die uncertain-Flag eines requires_review-Matches durch', () => {
    const run = buildLeversRun(
      rowsFor({
        steps: [
          step('s-alt', 'f-alt', 'Prüfen', { anzahlMA: 'Fertigung!H5' }),
          step('s-neu', 'f-neu', 'Prüfen', { anzahlMA: 'Fertigung!H7' }),
        ],
        stepMatches: [match('m-1', 's-alt', 's-neu', true)],
        diffs: [diff('m-1', 'anzahlMA', 2, 3, 0.5)],
      }),
    )
    expect(run.levers[0].uncertain).toBe(true)
    expect(run.anyUncertain).toBe(true)
  })

  it('baut je KPI-Feld den richtigen Hebel mit den unvermischten Werten des Felds', () => {
    // Einheiten-Grenzfläche: vier Felder mit vier Einheiten (s, BW/h, BW/h,
    // Personen) am selben Match — jeder Hebel trägt exakt die Werte SEINES
    // Felds und den zum Feld gehörenden Forderungstext.
    const cells = {
      zykluszeit: 'Fertigung!C7',
      mss: 'Fertigung!D7',
      lohnkosten: 'Fertigung!E7',
      anzahlMA: 'Fertigung!F7',
    }
    const run = buildLeversRun(
      rowsFor({
        steps: [
          step('s-alt', 'f-alt', 'Giessen', cells),
          step('s-neu', 'f-neu', 'Giessen', cells),
        ],
        stepMatches: [match('m-1', 's-alt', 's-neu')],
        diffs: [
          diff('m-1', 'zykluszeit', 12.5, 15, 0.2),
          diff('m-1', 'mss', 85, 102, 0.2),
          diff('m-1', 'lohnkosten', 28, 33.6, 0.2),
          diff('m-1', 'anzahlMA', 2, 3, 0.5),
        ],
      }),
    )
    expect(run.levers.map((l) => [l.kpiLabel, l.alt, l.neu])).toEqual([
      ['Zykluszeit', 12.5, 15],
      ['Maschinenstundensatz', 85, 102],
      ['Lohnkosten', 28, 33.6],
      ['Anzahl Mitarbeiter', 2, 3],
    ])
    expect(run.levers.map((l) => l.neuCell)).toEqual([
      'Fertigung!C7',
      'Fertigung!D7',
      'Fertigung!E7',
      'Fertigung!F7',
    ])
  })

  it('hält zwei gleichnamige Prozessschritte über die Match-IDs auseinander (kein Label-Join)', () => {
    const run = buildLeversRun(
      rowsFor({
        steps: [
          step('s-alt-1', 'f-alt', 'Schweissen', { zykluszeit: 'Fertigung!C5' }),
          step('s-neu-1', 'f-neu', 'Schweissen', { zykluszeit: 'Fertigung!C7' }),
          step('s-alt-2', 'f-alt', 'Schweissen', { zykluszeit: 'Fertigung!C15' }),
          step('s-neu-2', 'f-neu', 'Schweissen', { zykluszeit: 'Fertigung!C17' }),
        ],
        stepMatches: [
          match('m-1', 's-alt-1', 's-neu-1'),
          match('m-2', 's-alt-2', 's-neu-2'),
        ],
        diffs: [
          diff('m-1', 'zykluszeit', 10, 12, 0.2),
          diff('m-2', 'zykluszeit', 20, 24, 0.2),
        ],
      }),
    )
    expect(run.levers).toHaveLength(2)
    expect(run.levers.map((l) => [l.altCell, l.neuCell])).toEqual([
      ['Fertigung!C5', 'Fertigung!C7'],
      ['Fertigung!C15', 'Fertigung!C17'],
    ])
  })

  it('meldet belowThreshold, wenn KPI-Daten da sind, aber jedes Delta unter 5 % liegt', () => {
    const run = buildLeversRun(
      rowsFor({
        steps: [],
        stepMatches: [],
        diffs: [diff(null, 'zykluszeit', 100, 103, 0.03)],
      }),
    )
    expect(run.levers).toHaveLength(0)
    expect(run.belowThreshold).toBe(true)
  })

  it('meldet KEIN belowThreshold ohne vergleichbare KPI-Daten (Platzhalter-Fall)', () => {
    const run = buildLeversRun(
      rowsFor({
        steps: [],
        stepMatches: [],
        diffs: [diff(null, 'fk', 10, 12, 0.2)],
      }),
    )
    expect(run.levers).toHaveLength(0)
    expect(run.belowThreshold).toBe(false)
  })

  it('nimmt ein Delta von exakt 5 % als Hebel (Schwelle ist inklusiv)', () => {
    const run = buildLeversRun(
      rowsFor({
        steps: [],
        stepMatches: [],
        diffs: [diff(null, 'zykluszeit', 100, 105, 0.05)],
      }),
    )
    expect(run.levers).toHaveLength(1)
  })

  it('liefert exakt die Hebel des alten Schale-Wegs (Paritäts-Pin gegen buildLevers am Stück)', () => {
    // Referenz: der alte leverInput-Aufbau der Schale (Label-/Unsicherheits-
    // Maps + ein buildLevers-Aufruf über ALLE Zeilen). buildLeversRun ruft
    // buildLevers pro Zeile auf — dieser Pin beweist die Äquivalenz der
    // beiden Wege dauerhaft, inklusive Filter- und Reihenfolge-Verhalten.
    const steps = [
      step('s-alt', 'f-alt', 'Spritzguss', { zykluszeit: 'Fertigung!C5' }),
      step('s-neu', 'f-neu', 'Spritzguss', { zykluszeit: 'Fertigung!C7' }),
    ]
    const stepMatches = [match('m-1', 's-alt', 's-neu', true)]
    const diffs = [
      diff('m-1', 'zykluszeit', 10, 12, 0.2),
      diff('m-1', 'fk', 10, 12, 0.2),
      diff(null, 'mss', 100, 120, 0.2),
      diff('m-1', 'lohnkosten', 30, 30.9, 0.03),
    ]
    const stepLabelById = new Map(
      steps.map((s) => [s.id, s.process_name || s.position_number || s.id]),
    )
    const matchLabelById = new Map<string, string>()
    const matchUncertainById = new Map<string, boolean>()
    for (const m of stepMatches) {
      matchLabelById.set(
        m.id,
        (m.neu_step_id && stepLabelById.get(m.neu_step_id)) ||
          (m.alt_step_id && stepLabelById.get(m.alt_step_id)) ||
          '—',
      )
      matchUncertainById.set(m.id, Boolean(m.requires_review))
    }
    const altWeg = buildLevers(
      diffs.map((d) => ({
        stepLabel:
          (d.step_match_id ? matchLabelById.get(d.step_match_id) : null) ?? '—',
        field: d.field,
        alt_value: d.alt_value,
        neu_value: d.neu_value,
        delta_percent: d.delta_percent,
        uncertain: d.step_match_id
          ? (matchUncertainById.get(d.step_match_id) ?? false)
          : true,
      })),
    )
    const run = buildLeversRun(rowsFor({ diffs, steps, stepMatches }))
    expect(
      run.levers.map((l) => ({
        stepLabel: l.stepLabel,
        kpiLabel: l.kpiLabel,
        ask: l.ask,
        alt: l.alt,
        neu: l.neu,
        deltaPercent: l.deltaPercent,
        uncertain: l.uncertain,
      })),
    ).toEqual(altWeg)
  })
})
