// KAR-930 — header-parser.ts real-file regression (env-gated).
//
// Env-gated (describe.skipIf when the confidential input directory/manifest
// is absent — same discipline as multi-qaf-probe.manual.test.ts and
// qaf-type-detector.real-files.test.ts, which this file does NOT modify or
// depend on) real-file proof that parseVariantHeaderBlock reproduces the
// documented ground-truth AGGREGATE counts for all 4 known real Multi-QAF
// files (10-analyse-clarwe-eu.md/-mx.md/-nafta.md/-ncar.md).
//
// Confidentiality discipline (KAR-926 F2 precedent this file follows
// exactly): the 4 files' names are read from the SAME server-only,
// never-committed manifest qaf-type-detector.real-files.test.ts already
// reads (one filename per line) — never hardcoded here as string literals.
// Files are addressed by their MANIFEST POSITION (0-3, matching this PR's
// task brief "Datei 1..4" ordering) — never by name, codename, or content.
// This test asserts ONLY aggregate COUNTS (variant counts by active state,
// classification-kind counts) — never a filename, project/vehicle code,
// volume figure, or any other cell content, matching the Fixture-Daten-Regel
// discipline synthetic-fixtures.ts's own header establishes for this
// package.
//
// Per-file sheet selection (Datei 3 / NAFTA only): 10-analyse-nafta.md C
// documents that this file's per-variant dimension rows live in the
// MATERIAL sheet (mirrored into Zusammenfassung by formula only, with no
// separate dimension-row block of its own on the Summary sheet) — this is
// the "und ggf. BOM-Sheet" case the task brief names explicitly. The other
// 3 files' dimension rows live directly on the Summary sheet.
//
// tdd-guard:skip — real-file regression/observation test, not a unit-level
// behavior spec (behavior is unit-tested in header-parser.test.ts and
// column-classifier.test.ts via synthetic fixtures).

import { describe, it, expect } from 'vitest'
import { existsSync, readFileSync } from 'node:fs'
import path from 'node:path'
import { loadExcelWorkbook } from '../../workbook-adapter'
import { matchesModuleSheetName } from '../../module-sheet-names'
import { headerParserInputFromWorksheet, parseVariantHeaderBlock, type HeaderParserResult } from '../header-parser'

const INPUT_DIR = '/root/aria/work/qaf-compare-kar824/input'
const MULTI_QAF_MANIFEST = path.join(INPUT_DIR, 'multi-qaf-files.txt')

function readMultiQafManifest(): string[] {
  if (!existsSync(MULTI_QAF_MANIFEST)) return []
  return readFileSync(MULTI_QAF_MANIFEST, 'utf-8')
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => line !== '' && !line.startsWith('#'))
}

const MULTI_QAF_FILES: readonly string[] = readMultiQafManifest()

async function parseFile(index: number, sheetKind: 'SUMMARY' | 'MATERIAL', colFrom?: number): Promise<HeaderParserResult> {
  const fileName = MULTI_QAF_FILES[index]
  const buffer = readFileSync(path.join(INPUT_DIR, fileName))
  const wb = await loadExcelWorkbook(buffer)
  const ws = wb.worksheets.find((w) => matchesModuleSheetName(w.name, sheetKind))
  if (!ws) throw new Error(`Manifest file at index ${index} has no ${sheetKind} sheet`)
  const input = headerParserInputFromWorksheet(ws)
  return parseVariantHeaderBlock(input, colFrom !== undefined ? { colFrom, colTo: colFrom + 30 } : {})
}

function countByActiveState(result: HeaderParserResult): { active: number; inactive: number; reserved: number } {
  return {
    active: result.variants.filter((v) => v.activeState === 'active').length,
    inactive: result.variants.filter((v) => v.activeState === 'inactive').length,
    reserved: result.variants.filter((v) => v.activeState === 'reserved').length,
  }
}

function countByKind(result: HeaderParserResult): Record<string, number> {
  const counts: Record<string, number> = {}
  for (const c of result.columnClassifications) counts[c.kind] = (counts[c.kind] ?? 0) + 1
  return counts
}

describe.skipIf(!existsSync(INPUT_DIR) || !existsSync(MULTI_QAF_MANIFEST) || readMultiQafManifest().length < 4)(
  'KAR-930 — header-parser real-file regression (env-gated)',
  () => {
    it('Datei 1: Summary-Sheet ergibt genau 26 aktive Varianten, keine inaktiven/reservierten', async () => {
      const result = await parseFile(0, 'SUMMARY')
      const counts = countByActiveState(result)
      expect(counts).toEqual({ active: 26, inactive: 0, reserved: 0 })
    }, 60_000)

    it('Datei 2: Summary-Sheet ergibt 25 Slots (12 mit Identität: 10 aktiv + 2 inaktiv, 13 reserviert) plus mindestens 1 als delta klassifizierte Nicht-Varianten-Spalte', async () => {
      const result = await parseFile(1, 'SUMMARY')
      const counts = countByActiveState(result)
      expect(counts).toEqual({ active: 10, inactive: 2, reserved: 13 })
      expect(result.variants).toHaveLength(25)
      const kinds = countByKind(result)
      // KAR-930 review F1 fix side effect (documented here so a future
      // re-run of this env-gated test doesn't look like an unexplained
      // regression): this file's benchmark pseudo-slot column used to be
      // classified benchmark_scenario, but ONLY because a genuine (if
      // skewed) unrecognized per-column dimension row elsewhere in the
      // header block was being silently DROPPED WHOLESALE by the
      // pre-fix dominance guard — one of its leftover, unclaimed cells then
      // happened to bleed into that column's classifier `label` fact via
      // collectBodySignals, incidentally satisfying the
      // "literal + label -> benchmark_scenario" rule. The F1 fix keeps that
      // dimension row instead of dropping it (its real purpose: prevent it
      // from erasing a genuine per-column dimension for every OTHER
      // variant), so the value that used to leak into the benchmark
      // column's label now correctly becomes that column's own dimension
      // value instead — leaving the column with no formula and no genuine
      // descriptive label, which classifyColumn's own explicit
      // "never guessed into benchmark_scenario" doctrine (column-
      // classifier.ts) correctly resolves to `unknown` rather than a
      // lucky-guess `benchmark_scenario`. The column was never counted as a
      // variant either way (both kinds are non-variant), so the file's real
      // variant/active/inactive/reserved counts above are unaffected — only
      // this one column's non-variant sub-classification changed from an
      // accidental match to a doctrine-compliant one. AC (the real
      // delta-helper column referencing the benchmark slot) still correctly
      // resolves to delta_column, which this assertion continues to check.
      const benchmarkOrDeltaCount = (kinds.benchmark_scenario ?? 0) + (kinds.delta_column ?? 0)
      expect(benchmarkOrDeltaCount).toBeGreaterThanOrEqual(1)
      expect(kinds.delta_column ?? 0).toBeGreaterThanOrEqual(1)
    }, 60_000)

    it('Datei 3: Material-Sheet (Dimension-Block lebt dort, nicht im Summary) ergibt genau 5 aktive Varianten', async () => {
      // 10-analyse-nafta.md C: this file's per-variant dimension rows live
      // in the MATERIAL sheet at a different column offset than the Summary
      // sheet's own AW_SCAN_TO-derived default (real finding: MATERIAL's
      // variant band starts 3 columns earlier than Summary's) — the "ggf.
      // BOM-Sheet" case the task brief names explicitly.
      const result = await parseFile(2, 'MATERIAL', 13)
      const counts = countByActiveState(result)
      expect(counts).toEqual({ active: 5, inactive: 0, reserved: 0 })
    }, 60_000)

    it('Datei 4: Summary-Sheet ergibt genau 8 aktive Varianten, keine inaktiven/reservierten', async () => {
      const result = await parseFile(3, 'SUMMARY')
      const counts = countByActiveState(result)
      expect(counts).toEqual({ active: 8, inactive: 0, reserved: 0 })
    }, 60_000)
  },
)
