// Tests for the LOGISTICS&CUSTOM sheet parser + Reconciliation (KAR-903 /
// P2.4). Fixtures are entirely synthetic (invented delivery sites / cost
// values) — no real Brose/Kiekert/Autoliv/BMW data or files. // allow-customer-string

import { describe, it, expect } from 'vitest'
import ExcelJS from 'exceljs'
import {
  isLogisticsSheetName,
  findLogisticsWorksheet,
  findLogisticsHeaderRow,
  matchLogisticsHeaderColumn,
  parseLogisticsWorksheet,
  parseLogisticsSheet,
  logisticsRowsForReconciliation,
  logisticsRowsFromPersistedMeta,
  validateLogisticsCostFormula,
  validateLogisticsIncotermDomain,
  evaluateLogisticsValidation,
  logisticsValidationResultToPlausibilityIssue,
  checkLogisticsValidation,
  CORE_LOGISTICS_FIELD_KEYS,
  LOGISTICS_INCOTERM_VALUES,
  LOGISTICS_VALIDATION_CONFIG,
  type LogisticsRow,
  type LogisticsRowValues,
} from '../logistics-parser'

// Header labels in canonical-fields.ts LOGISTICS_FIELDS declaration order
// (Leitfaden Teil 2 [40]).
const FULL_LOGISTICS_HEADERS = [
  'Positionsnummer', // 0 positionNumber
  'Anlieferstandort', // 1 deliverySite
  'Gesamtvolumen', // 2 totalVolume
  'Angebotswährung AW', // 3 quotationCurrency
  'Transportkosten pro Bauteil Lieferant', // 4 transportCostPerPart
  'Lieferbedingungen', // 5 deliveryTerms
  'Verpackungskosten pro Bauteil Lieferant', // 6 packagingCostPerPart
  'Zuschlagsschlüssel (Verpackungskosten)', // 7 surchargeKeyPackaging
  'Vorverpackungskosten pro Bauteil Lieferant', // 8 prePackagingCostPerPart
  'Zuschlagsschlüssel (Vorverpackungskosten)', // 9 surchargeKeyPrePackaging
  'Logistikkosten je Anlieferstandort', // 10 logisticsCostPerDeliverySite
  'Zollkosten pro Bauteil je Anlieferstandort', // 11 customsCostPerPart
  'Zolltarifnummer', // 12 customsTariffNumber
  'Zollkosten je Anlieferstandort', // 13 customsCostPerDeliverySite
]

async function workbookWithSheets(sheets: Array<{ name: string; rows: unknown[][] }>): Promise<ExcelJS.Workbook> {
  const wb = new ExcelJS.Workbook()
  for (const { name, rows } of sheets) {
    const ws = wb.addWorksheet(name)
    rows.forEach((row, i) => {
      ws.getRow(i + 1).values = row as ExcelJS.CellValue[]
    })
  }
  return wb
}

/**
 * 3-row synthetic LOGISTICS&CUSTOM worked example — 15 rows are documented
 * (Leitfaden [41] Abbildung 25), 3 suffice to exercise Sigma-checks + formula
 * validation. Every Logistikkosten je Anlieferstandort value below is
 * consistent with Transportkosten + Verpackungskosten + Vorverpackungskosten
 * (Leitfaden [40] Formeln), and every Zollkosten pro Bauteil equals the
 * Zollkosten je Anlieferstandort (single-unit synthetic volumes — the
 * per-part/per-site distinction is not independently checkable from the
 * documented field set, see module header).
 */
function syntheticLogisticsRows(): unknown[][] {
  return [
    FULL_LOGISTICS_HEADERS,
    ['1', 'Spartanburg', 50000, 'EUR', 2.0, 'FCA', 0.5, 'VERP', 0.3, 'LVVV', 2.8, 0.4, '8708299000', 0.4],
    ['2', 'Shenyang', 30000, 'EUR', 3.1, 'DAP', 0.2, 'VERP', 0, '', 3.3, 0.6, '8708299000', 0.6],
    ['3', 'Leipzig', 20000, 'EUR', 1.0, 'DDP', 0, '', 0, '', 1.0, 0.1, '8708299000', 0.1],
  ]
}

async function cleanLogisticsWorksheet(): Promise<ExcelJS.Worksheet> {
  const wb = await workbookWithSheets([{ name: 'LOGISTICS&CUSTOM', rows: syntheticLogisticsRows() }])
  return wb.worksheets[0]
}

// ── Sheet detection ──────────────────────────────────────────────────────

describe('isLogisticsSheetName', () => {
  it('matches the documented tab name (Leitfaden [39]: Blatt "LOGISTICS&CUSTOM")', () => {
    expect(isLogisticsSheetName('LOGISTICS&CUSTOM')).toBe(true)
    expect(isLogisticsSheetName('logistics&custom')).toBe(true)
    expect(isLogisticsSheetName('Logistics')).toBe(true)
  })

  it('does not match unrelated sheet names', () => {
    expect(isLogisticsSheetName('MATERIAL')).toBe(false)
    expect(isLogisticsSheetName('SUMMARY')).toBe(false)
    expect(isLogisticsSheetName('RAW MATERIAL RISKS')).toBe(false)
  })
})

describe('findLogisticsWorksheet', () => {
  it('finds the LOGISTICS&CUSTOM sheet by name', async () => {
    const wb = await workbookWithSheets([
      { name: 'SUMMARY', rows: [['x']] },
      { name: 'LOGISTICS&CUSTOM', rows: syntheticLogisticsRows() },
    ])
    const ws = findLogisticsWorksheet(wb)
    expect(ws?.name).toBe('LOGISTICS&CUSTOM')
  })

  it('returns null when the workbook has no LOGISTICS sheet', async () => {
    const wb = await workbookWithSheets([{ name: 'SUMMARY', rows: [['x']] }])
    expect(findLogisticsWorksheet(wb)).toBeNull()
  })
})

// ── Header matching ──────────────────────────────────────────────────────

describe('matchLogisticsHeaderColumn', () => {
  it('matches every documented DE label to its field key', async () => {
    const expected: Array<[string, string]> = [
      ['Positionsnummer', 'positionNumber'],
      ['Anlieferstandort', 'deliverySite'],
      ['Gesamtvolumen', 'totalVolume'],
      ['Angebotswährung AW', 'quotationCurrency'],
      ['Transportkosten pro Bauteil Lieferant', 'transportCostPerPart'],
      ['Lieferbedingungen', 'deliveryTerms'],
      ['Verpackungskosten pro Bauteil Lieferant', 'packagingCostPerPart'],
      ['Zuschlagsschlüssel (Verpackungskosten)', 'surchargeKeyPackaging'],
      ['Vorverpackungskosten pro Bauteil Lieferant', 'prePackagingCostPerPart'],
      ['Zuschlagsschlüssel (Vorverpackungskosten)', 'surchargeKeyPrePackaging'],
      ['Logistikkosten je Anlieferstandort', 'logisticsCostPerDeliverySite'],
      ['Zollkosten pro Bauteil je Anlieferstandort', 'customsCostPerPart'],
      ['Zolltarifnummer', 'customsTariffNumber'],
      ['Zollkosten je Anlieferstandort', 'customsCostPerDeliverySite'],
    ]
    for (const [label, key] of expected) {
      const match = await matchLogisticsHeaderColumn(label)
      expect(match?.key, `label "${label}"`).toBe(key)
    }
  })

  it('returns null for an empty or unrecognized header cell', async () => {
    expect(await matchLogisticsHeaderColumn('')).toBeNull()
    expect(await matchLogisticsHeaderColumn('Voellig unbekannte Spalte')).toBeNull()
  })
})

describe('findLogisticsHeaderRow', () => {
  it('locates the header row in the first rows of the grid', async () => {
    const grid = syntheticLogisticsRows()
    const idx = await findLogisticsHeaderRow(grid)
    expect(idx).toBe(0)
  })

  it('returns null for an empty grid', async () => {
    expect(await findLogisticsHeaderRow([])).toBeNull()
  })
})

// ── Row parsing ──────────────────────────────────────────────────────────

describe('parseLogisticsWorksheet', () => {
  it('parses all data rows with correct field values', async () => {
    const ws = await cleanLogisticsWorksheet()
    const result = await parseLogisticsWorksheet(ws)
    expect(result.coreFieldsFound).toBe(true)
    expect(result.length).toBe(3)

    const [row1, row2, row3] = result
    expect(row1.positionNumber).toBe('1')
    expect(row1.deliverySite).toBe('Spartanburg')
    expect(row1.totalVolume).toBe(50000)
    expect(row1.quotationCurrency).toBe('EUR')
    expect(row1.transportCostPerPart).toBe(2.0)
    expect(row1.deliveryTerms).toBe('FCA')
    expect(row1.packagingCostPerPart).toBe(0.5)
    expect(row1.surchargeKeyPackaging).toBe('VERP')
    expect(row1.prePackagingCostPerPart).toBe(0.3)
    expect(row1.surchargeKeyPrePackaging).toBe('LVVV')
    expect(row1.logisticsCostPerDeliverySite).toBe(2.8)
    expect(row1.customsCostPerPart).toBe(0.4)
    expect(row1.customsTariffNumber).toBe('8708299000')
    expect(row1.customsCostPerDeliverySite).toBe(0.4)

    expect(row2.deliverySite).toBe('Shenyang')
    expect(row3.deliverySite).toBe('Leipzig')
  })

  it('carries sourceCells/normalized/rawText provenance from day one', async () => {
    const ws = await cleanLogisticsWorksheet()
    const [row] = await parseLogisticsWorksheet(ws)
    expect(row.sourceCells.deliverySite).toBe('LOGISTICS&CUSTOM!B2')
    expect(row.normalized.deliverySite).toBe('Spartanburg')
    expect(row.rawText).toEqual({})
  })

  it('stops at the first fully blank row', async () => {
    const rows = [...syntheticLogisticsRows()]
    rows.splice(2, 0, ['', '', '', '', '', '', '', '', '', '', '', '', '', ''])
    const wb = await workbookWithSheets([{ name: 'LOGISTICS&CUSTOM', rows }])
    const result = await parseLogisticsWorksheet(wb.worksheets[0])
    expect(result.length).toBe(1)
  })

  it('row-push guard: a row without Anlieferstandort is not pushed (footer/subtotal row)', async () => {
    const rows = [
      FULL_LOGISTICS_HEADERS,
      ['1', 'Spartanburg', 50000, 'EUR', 2.0, 'FCA', 0.5, 'VERP', 0.3, 'LVVV', 2.8, 0.4, '8708299000', 0.4],
      ['', '', '', '', '', '', '', '', '', '', 2.8, '', '', ''], // subtotal-like row, no delivery site
    ]
    const wb = await workbookWithSheets([{ name: 'LOGISTICS&CUSTOM', rows }])
    const result = await parseLogisticsWorksheet(wb.worksheets[0])
    expect(result.length).toBe(1)
  })

  it('returns empty + coreFieldsFound:false for a sheet with too degraded a header', async () => {
    const wb = await workbookWithSheets([
      { name: 'LOGISTICS&CUSTOM', rows: [['Voellig', 'Andere', 'Spalten'], ['a', 'b', 'c']] },
    ])
    const result = await parseLogisticsWorksheet(wb.worksheets[0])
    expect(result.coreFieldsFound).toBe(false)
    expect(result.length).toBe(0)
    expect(result.degradation).toBeUndefined() // below MIN_SIGNAL_MAPPED_COLUMNS — genuinely foreign, no degradation to report
  })

  // KAR-958/P2 (gate-audit.md B8, coreFieldsFound-Resilienz): dropping only
  // the non-identity core field (logisticsCostPerDeliverySite) — deliverySite
  // (the row-push guard field) stays intact — must extract rows instead of
  // discarding the other 12 already-mapped columns.
  it('extracts rows when only a NON-identity core field is missing (logisticsCostPerDeliverySite)', async () => {
    const rows = syntheticLogisticsRows()
    const headers = [...rows[0]]
    const costIdx = headers.indexOf('Logistikkosten je Anlieferstandort')
    const withoutCost = rows.map((row) => {
      const r = [...row]
      r.splice(costIdx, 1)
      return r
    })
    const wb = await workbookWithSheets([{ name: 'LOGISTICS&CUSTOM', rows: withoutCost }])
    const result = await parseLogisticsWorksheet(wb.worksheets[0])

    expect(result.coreFieldsFound).toBe(false)
    expect(result.length).toBe(3) // the fix: all 3 rows survive
    expect(result.every((r) => r.deliverySite !== '')).toBe(true)
    expect(result.every((r) => r.logisticsCostPerDeliverySite === null)).toBe(true)
    expect(result.degradation).toEqual({
      facet: 'logistics',
      reason: 'PARSE_FAILED',
      sheet: 'LOGISTICS&CUSTOM',
      message: expect.stringContaining('logisticsCostPerDeliverySite'),
    })
  })

  it('returns empty result for a grid with fewer than 2 rows', async () => {
    const wb = await workbookWithSheets([{ name: 'LOGISTICS&CUSTOM', rows: [FULL_LOGISTICS_HEADERS] }])
    const result = await parseLogisticsWorksheet(wb.worksheets[0])
    expect(result.length).toBe(0)
    expect(result.coreFieldsFound).toBe(false)
  })
})

describe('parseLogisticsSheet', () => {
  it('returns null when the workbook has no LOGISTICS sheet at all', async () => {
    const wb = await workbookWithSheets([{ name: 'SUMMARY', rows: [['x']] }])
    expect(await parseLogisticsSheet(wb)).toBeNull()
  })

  it('finds + parses the sheet from a full workbook', async () => {
    const wb = await workbookWithSheets([
      { name: 'SUMMARY', rows: [['x']] },
      { name: 'LOGISTICS&CUSTOM', rows: syntheticLogisticsRows() },
    ])
    const result = await parseLogisticsSheet(wb)
    expect(result?.length).toBe(3)
    expect(result?.coreFieldsFound).toBe(true)
  })
})

// ── Tri-state reconciliation bridge (day-1, KAR-902/#274 lesson) ──────────

describe('logisticsRowsForReconciliation', () => {
  it('returns null when parsed is null (no sheet at all)', () => {
    expect(logisticsRowsForReconciliation(null)).toBeNull()
  })

  it('returns null when coreFieldsFound is false (degraded header), even if the array is non-empty by construction', async () => {
    const wb = await workbookWithSheets([
      { name: 'LOGISTICS&CUSTOM', rows: [['Voellig', 'Andere', 'Spalten'], ['a', 'b', 'c']] },
    ])
    const parsed = await parseLogisticsWorksheet(wb.worksheets[0])
    expect(logisticsRowsForReconciliation(parsed)).toBeNull()
  })

  it('returns the rows array (possibly empty) when coreFieldsFound is true', async () => {
    const ws = await cleanLogisticsWorksheet()
    const parsed = await parseLogisticsWorksheet(ws)
    const rows = logisticsRowsForReconciliation(parsed)
    expect(rows).toHaveLength(3)
  })
})

describe('logisticsRowsFromPersistedMeta', () => {
  it('returns undefined when meta is undefined (no parse attempted, pre-KAR-903 file)', () => {
    expect(logisticsRowsFromPersistedMeta(undefined)).toBeUndefined()
  })

  it('returns null when meta is null (parse attempted, no sheet found)', () => {
    expect(logisticsRowsFromPersistedMeta(null)).toBeNull()
  })

  it('reconstructs rows from persisted rows + parseMeta, delegating to logisticsRowsForReconciliation', async () => {
    const ws = await cleanLogisticsWorksheet()
    const parsed = await parseLogisticsWorksheet(ws)
    const persisted = {
      rows: [...parsed],
      parseMeta: {
        parseConfidence: parsed.parseConfidence,
        unmappedHeaders: parsed.unmappedHeaders,
        mappedFieldCount: parsed.mappedFieldCount,
        coreFieldsFound: parsed.coreFieldsFound,
      },
    }
    const rows = logisticsRowsFromPersistedMeta(persisted)
    expect(rows).toHaveLength(3)
  })

  it('collapses a persisted degraded header (coreFieldsFound: false) to null, not the raw rows array', () => {
    const persisted = {
      rows: [{ positionNumber: '1' } as unknown as LogisticsRow],
      parseMeta: { parseConfidence: 0, unmappedHeaders: [], mappedFieldCount: 1, coreFieldsFound: false },
    }
    expect(logisticsRowsFromPersistedMeta(persisted)).toBeNull()
  })
})

// ── CORE_LOGISTICS_FIELD_KEYS ───────────────────────────────────────────

describe('CORE_LOGISTICS_FIELD_KEYS', () => {
  it('is positionNumber + deliverySite + logisticsCostPerDeliverySite', () => {
    expect(CORE_LOGISTICS_FIELD_KEYS).toEqual(['positionNumber', 'deliverySite', 'logisticsCostPerDeliverySite'])
  })
})

// ── Formel-Validierung: Logistikkosten je Anlieferstandort (Leitfaden [40]) ─

function mkLogisticsRow(o: Partial<LogisticsRowValues> = {}): LogisticsRow {
  const base: LogisticsRowValues = {
    positionNumber: '1',
    deliverySite: 'Spartanburg',
    totalVolume: 50000,
    quotationCurrency: 'EUR',
    transportCostPerPart: 2.0,
    deliveryTerms: 'FCA',
    packagingCostPerPart: 0.5,
    surchargeKeyPackaging: 'VERP',
    prePackagingCostPerPart: 0.3,
    surchargeKeyPrePackaging: 'LVVV',
    logisticsCostPerDeliverySite: 2.8,
    customsCostPerPart: 0.4,
    customsTariffNumber: '8708299000',
    customsCostPerDeliverySite: 0.4,
  }
  return { ...base, ...o, sourceCells: {}, normalized: {}, rawText: (o as LogisticsRow).rawText ?? {} }
}

describe('validateLogisticsCostFormula (Transport + Verpackung + Vorverpackung = Logistikkosten je Anlieferstandort)', () => {
  it('bestanden when the sheet-shown total matches the recomputation within tolerance', () => {
    const r = validateLogisticsCostFormula(mkLogisticsRow(), 0, 'ALT')
    expect(r?.status).toBe('bestanden')
    expect(r?.expected).toBe(2.8)
    expect(r?.actual).toBe(2.8)
  })

  it('abweichung when the sheet-shown total deviates beyond tolerance', () => {
    const row = mkLogisticsRow({ logisticsCostPerDeliverySite: 5 })
    const r = validateLogisticsCostFormula(row, 0, 'NEU')
    expect(r?.status).toBe('abweichung')
    expect(r?.messageDe).toContain('Logistikkosten')
  })

  it('is gated: returns null when logisticsCostPerDeliverySite itself is empty (nothing to check yet)', () => {
    const row = mkLogisticsRow({ logisticsCostPerDeliverySite: null })
    expect(validateLogisticsCostFormula(row, 0, 'ALT')).toBeNull()
  })

  it('nicht_pruefbar when an input component is missing', () => {
    const row = mkLogisticsRow({ transportCostPerPart: null })
    const r = validateLogisticsCostFormula(row, 0, 'ALT')
    expect(r?.status).toBe('nicht_pruefbar')
    expect(r?.reason).toContain('Transportkosten')
  })

  it('treats zero packaging/pre-packaging costs as valid inputs, not missing', () => {
    const row = mkLogisticsRow({
      packagingCostPerPart: 0,
      prePackagingCostPerPart: 0,
      transportCostPerPart: 1.0,
      logisticsCostPerDeliverySite: 1.0,
    })
    const r = validateLogisticsCostFormula(row, 0, 'ALT')
    expect(r?.status).toBe('bestanden')
  })
})

// ── Incoterm-Domänenliste (Leitfaden [39]/[40]: FCA/DAP/DDP) ──────────────

describe('LOGISTICS_INCOTERM_VALUES', () => {
  it('is exactly the three Incoterms the Leitfaden documents for this sheet', () => {
    expect(LOGISTICS_INCOTERM_VALUES).toEqual(['FCA', 'DAP', 'DDP'])
  })
})

describe('validateLogisticsIncotermDomain', () => {
  it('bestanden for a known Incoterm value', () => {
    const r = validateLogisticsIncotermDomain(mkLogisticsRow({ deliveryTerms: 'DDP' }), 0, 'ALT')
    expect(r?.status).toBe('bestanden')
  })

  it('is case-insensitive against the documented domain list', () => {
    const r = validateLogisticsIncotermDomain(mkLogisticsRow({ deliveryTerms: 'ddp' }), 0, 'ALT')
    expect(r?.status).toBe('bestanden')
  })

  it('pruefen (hint, not error) for an unknown Incoterm value', () => {
    const r = validateLogisticsIncotermDomain(mkLogisticsRow({ deliveryTerms: 'EXW' }), 0, 'NEU')
    expect(r?.status).toBe('pruefen')
    expect(r?.messageDe).toContain('EXW')
  })

  it('is gated: returns null when deliveryTerms is empty (nothing to check yet)', () => {
    const r = validateLogisticsIncotermDomain(mkLogisticsRow({ deliveryTerms: '' }), 0, 'ALT')
    expect(r).toBeNull()
  })
})

// ── Orchestrator + PlausibilityIssue bridge ────────────────────────────────

describe('evaluateLogisticsValidation', () => {
  it('returns no results at all when logisticsRows is undefined (no parse attempted)', () => {
    expect(evaluateLogisticsValidation({ side: 'ALT' })).toEqual([])
  })

  it('returns one file-level nicht_pruefbar result for each check family when logisticsRows is null', () => {
    const results = evaluateLogisticsValidation({ side: 'ALT', logisticsRows: null })
    expect(results.every((r) => r.status === 'nicht_pruefbar')).toBe(true)
    expect(results.length).toBeGreaterThan(0)
  })

  it('evaluates both checks per row for a confirmed rows array', () => {
    const results = evaluateLogisticsValidation({
      side: 'ALT',
      logisticsRows: [mkLogisticsRow(), mkLogisticsRow({ deliveryTerms: 'EXW' })],
    })
    expect(results.some((r) => r.checkId === 'log_calc_cost_per_delivery_site')).toBe(true)
    expect(results.some((r) => r.checkId === 'log_incoterm_domain')).toBe(true)
  })
})

describe('checkLogisticsValidation / logisticsValidationResultToPlausibilityIssue', () => {
  it('bestanden never produces an issue', () => {
    const issues = checkLogisticsValidation({ side: 'ALT', logisticsRows: [mkLogisticsRow()] })
    expect(issues.some((i) => i.type === 'log_calc_cost_per_delivery_site')).toBe(false)
    expect(issues.some((i) => i.type === 'log_incoterm_domain')).toBe(false)
  })

  it('a formula abweichung produces a "pruefen" severity issue', () => {
    const issues = checkLogisticsValidation({
      side: 'ALT',
      logisticsRows: [mkLogisticsRow({ logisticsCostPerDeliverySite: 99 })],
    })
    const issue = issues.find((i) => i.type === 'log_calc_cost_per_delivery_site')
    expect(issue?.severity).toBe('pruefen')
  })

  it('an unknown Incoterm produces a "pruefen" severity issue (hint, not error)', () => {
    const issues = checkLogisticsValidation({
      side: 'NEU',
      logisticsRows: [mkLogisticsRow({ deliveryTerms: 'CIF' })],
    })
    const issue = issues.find((i) => i.type === 'log_incoterm_domain')
    expect(issue?.severity).toBe('pruefen')
  })

  it('nicht_pruefbar produces a "hinweis" severity issue', () => {
    const issues = checkLogisticsValidation({ side: 'ALT', logisticsRows: null })
    expect(issues.every((i) => i.severity === 'hinweis')).toBe(true)
  })
})

// KAR-906/P3.2: log_* issues (abweichung AND nicht_pruefbar) carry an EN
// counterpart, field names sourced from the LOGISTICS canonical registry.
describe('checkLogisticsValidation — bilingual (KAR-906)', () => {
  it('a cost-formula "abweichung" issue carries explanationEn distinct from explanation', () => {
    const issues = checkLogisticsValidation({
      side: 'ALT',
      logisticsRows: [mkLogisticsRow({ logisticsCostPerDeliverySite: 99 })],
    })
    const issue = issues.find((i) => i.type === 'log_calc_cost_per_delivery_site')
    expect(issue?.explanationEn).toBeTruthy()
    expect(issue?.explanationEn).not.toBe(issue?.explanation)
    expect(issue?.explanationEn).toMatch(/Logistics costs per delivery site/i)
  })

  it('an unknown-Incoterm issue carries explanationEn', () => {
    const issues = checkLogisticsValidation({ side: 'NEU', logisticsRows: [mkLogisticsRow({ deliveryTerms: 'CIF' })] })
    const issue = issues.find((i) => i.type === 'log_incoterm_domain')
    expect(issue?.explanationEn).toMatch(/Delivery terms/i)
  })

  it('a row-level "nicht_pruefbar" issue names the missing field in English', () => {
    const issues = checkLogisticsValidation({
      side: 'ALT',
      logisticsRows: [mkLogisticsRow({ transportCostPerPart: null, logisticsCostPerDeliverySite: 1 })],
    })
    const issue = issues.find((i) => i.type === 'log_calc_cost_per_delivery_site_nicht_pruefbar')
    expect(issue?.explanationEn).toMatch(/Transport/i)
  })

  it('the file-level "no sheet" nicht_pruefbar has an English reason', () => {
    const issues = checkLogisticsValidation({ side: 'ALT', logisticsRows: null })
    expect(issues.every((i) => i.explanationEn?.includes('No LOGISTICS&CUSTOM sheet detected'))).toBe(true)
  })
})

describe('LOGISTICS_VALIDATION_CONFIG', () => {
  it('is the STARTWERT-Fachentscheid tolerance (0.5% / 1 AW), same as RMR/reconciliation', () => {
    expect(LOGISTICS_VALIDATION_CONFIG.relativeTolerance).toBe(0.005)
    expect(LOGISTICS_VALIDATION_CONFIG.absoluteToleranceMinor).toBe(1)
  })
})
