// Structural + round-trip + identity-stability tests over the 4 synthetic
// Multi-QAF container fixtures (KAR-929 / Multi-QAF-Programm P1.1).
//
// Each fixture reproduces one real analyzed workbook's STRUCTURAL pattern
// (see synthetic-fixtures.ts doc comments for the exact real-file finding
// each one stands in for) with synthetic data — no real customer codenames,
// no real commercial figures. See synthetic-fixtures.ts's "Fixture-Daten-
// Regel" header comment before adding any new literal here.

import { describe, expect, it } from 'vitest'
import { buildCompositeCanonicalKey, deriveActiveState, makeDimensionValue } from '../identity'
import { deserializeMultiQafContainer, serializeMultiQafContainer } from '../serialization'
import type { MultiQafContainer } from '../types'
import { buildMultiRowHeaderFixture, buildSharedManufacturingFixture, buildSplitColumnFixture, buildWideSlotFixture, SYNTHETIC_FIXTURES } from './synthetic-fixtures'

describe.each(Object.entries(SYNTHETIC_FIXTURES))('synthetic fixture: %s', (_name, build) => {
  let container: MultiQafContainer

  it('builds without throwing', () => {
    container = build()
    expect(container).toBeDefined()
  })

  it('round-trips through JSON serialization byte-for-byte (deep equal)', () => {
    const c = build()
    const restored = deserializeMultiQafContainer(serializeMultiQafContainer(c))
    expect(restored).toEqual(c)
  })

  it('every activeVariant has a unique, non-empty compositeCanonicalKey', () => {
    const c = build()
    const keys = c.activeVariants.map((v) => v.compositeCanonicalKey)
    expect(keys.every((k) => k !== '')).toBe(true)
    expect(new Set(keys).size).toBe(keys.length)
  })

  it('every activeVariant is classified "active" and every fully-empty inactiveVariant is "reserved"', () => {
    const c = build()
    for (const v of c.activeVariants) expect(v.activeState).toBe('active')
    for (const v of c.inactiveVariants) expect(['inactive', 'reserved']).toContain(v.activeState)
  })

  it('confidence values stay within [0, 1]', () => {
    const c = build()
    expect(c.confidence).toBeGreaterThanOrEqual(0)
    expect(c.confidence).toBeLessThanOrEqual(1)
    for (const v of [...c.activeVariants, ...c.inactiveVariants]) {
      expect(v.confidence).toBeGreaterThanOrEqual(0)
      expect(v.confidence).toBeLessThanOrEqual(1)
    }
  })
})

// ── Pattern-specific structural assertions ──────────────────────────────────

describe('wide-slot fixture (25-slot reserved-capacity pattern)', () => {
  it('reproduces the real 25/12/10 slot breakdown', () => {
    const c = buildWideSlotFixture()
    const identityBearing = c.activeVariants.length + c.inactiveVariants.filter((v) => v.activeState === 'inactive').length
    const reserved = c.inactiveVariants.filter((v) => v.activeState === 'reserved').length
    const total = identityBearing + reserved
    expect(total).toBe(25)
    expect(identityBearing).toBe(12)
    expect(c.activeVariants).toHaveLength(10)
    expect(reserved).toBe(13)
  })

  it('classifies the benchmark and comparison scenario columns outside the numbered slot range', () => {
    const c = buildWideSlotFixture()
    const kinds = c.auxiliaryScenarios.map((a) => a.kind)
    expect(kinds).toContain('benchmark_scenario')
    expect(kinds).toContain('comparison_scenario')
  })

  it('classifies delta/percentage-delta helper columns separately from the scenario columns', () => {
    const c = buildWideSlotFixture()
    const kinds = c.helperColumns.map((h) => h.kind)
    expect(kinds).toContain('delta_column')
    expect(kinds).toContain('percentage_delta_column')
  })

  it('models L-Shape as formula-backed and I-Shape as a literal (asymmetric profile modeling)', () => {
    const c = buildWideSlotFixture()
    const lShape = c.sharedManufacturingProfiles.find((p) => p.kind === 'lShape')
    const iShape = c.sharedManufacturingProfiles.find((p) => p.kind === 'iShape')
    expect(lShape?.formulaAndCachedValue?.formula).not.toBeNull()
    expect(iShape?.formulaAndCachedValue?.formula).toBeNull()
  })
})

describe('multi-row-header fixture (8-variant deep header-block pattern)', () => {
  it('has exactly 8 active variants and a 9-row header block', () => {
    const c = buildMultiRowHeaderFixture()
    expect(c.activeVariants).toHaveLength(8)
    expect(c.templateFingerprint.headerRowCount).toBe(9)
  })

  it('extends to a previously-unseen dimension key with zero code change (axleCode)', () => {
    const c = buildMultiRowHeaderFixture()
    expect(c.activeVariants.every((v) => v.dimensions.axleCode !== undefined)).toBe(true)
  })

  it('has an orphaned hidden setup-cost profile with zero variant bindings pointing to it', () => {
    const c = buildMultiRowHeaderFixture()
    const orphanedProfile = c.setupCostProfiles.find((p) => p.kind === 'setupCost')
    expect(orphanedProfile).toBeDefined()
    const boundProfileIds = new Set(c.variantProfileBindings.map((b) => b.profileId))
    expect(boundProfileIds.has(orphanedProfile!.profileId)).toBe(false)
    expect(c.warnings.some((w) => w.code === 'orphaned_hidden_sheet')).toBe(true)
  })

  it('has 3 hardcoded volume-band manufacturing profiles bound via cellReference evidence', () => {
    const c = buildMultiRowHeaderFixture()
    expect(c.sharedManufacturingProfiles.filter((p) => p.kind === 'volumeBand')).toHaveLength(3)
    const bindings = c.variantProfileBindings.filter((b) => b.profileId.startsWith('multirow-block-'))
    expect(bindings.every((b) => b.bindingEvidence.kind === 'cellReference')).toBe(true)
  })
})

describe('shared-manufacturing fixture (5-variant single-shared-profile pattern)', () => {
  it('has exactly 5 active variants, all bound to the SAME single manufacturing profile', () => {
    const c = buildSharedManufacturingFixture()
    expect(c.activeVariants).toHaveLength(5)
    expect(c.sharedManufacturingProfiles).toHaveLength(1)
    const profileIds = new Set(c.variantProfileBindings.map((b) => b.profileId))
    expect(profileIds.size).toBe(1)
    expect(c.variantProfileBindings).toHaveLength(5)
  })

  it('binds every variant to the identical absolute source cell', () => {
    const c = buildSharedManufacturingFixture()
    const cells = c.variantProfileBindings.map((b) => b.bindingEvidence.source?.cell)
    expect(new Set(cells).size).toBe(1)
  })

  it('records the filename-vs-content currency mismatch as a warning, never as a classification input', () => {
    const c = buildSharedManufacturingFixture()
    expect(c.warnings.some((w) => w.code === 'currency_mismatch_with_filename_expectation')).toBe(true)
    expect(c.currencies).toEqual(['CNY'])
  })
})

describe('split-column fixture (26/28-split orphaned-column pattern)', () => {
  it('has 28 active variants total: 26 linked + 2 orphaned', () => {
    const c = buildSplitColumnFixture()
    expect(c.activeVariants).toHaveLength(28)
    const linked = c.activeVariants.filter((v) => v.originalVariantNumber !== null)
    const orphaned = c.activeVariants.filter((v) => v.originalVariantNumber === null)
    expect(linked).toHaveLength(26)
    expect(orphaned).toHaveLength(2)
  })

  it('flags each orphaned variant with a variant_present_in_detail_absent_from_summary warning', () => {
    const c = buildSplitColumnFixture()
    const flagged = c.warnings.filter((w) => w.code === 'variant_present_in_detail_absent_from_summary')
    expect(flagged).toHaveLength(2)
  })

  it('records the SUMIF-vs-AVERAGE column-range formula inconsistency as a lineage note', () => {
    const c = buildSplitColumnFixture()
    expect(c.summaryAggregation.formulaLineageNotes.length).toBeGreaterThan(0)
  })

  it('has no M-QAF version marker (structural-only detection) yet a resolved template family', () => {
    const c = buildSplitColumnFixture()
    expect(c.multiQafVersion).toBeNull()
    expect(c.detectedTemplateFamily).toBe('qaf_8_1_custom_multi')
  })
})

// ── compositeCanonicalKey stability across reorder/rename (Master-Prompt §9) ─

describe('compositeCanonicalKey stability across a simulated ALT/NEU reorder', () => {
  it('the same logical variant keeps the same key when its column position changes between two container snapshots', () => {
    const altDims = { vehicle: makeDimensionValue('Q71'), driveType: makeDimensionValue('RWD') }
    const neuDims = { vehicle: makeDimensionValue('Q71'), driveType: makeDimensionValue('RWD') }
    // ALT: column Q (17). NEU: same variant reordered to column AF (32).
    const altKey = buildCompositeCanonicalKey(altDims)
    const neuKey = buildCompositeCanonicalKey(neuDims)
    expect(altKey).toBe(neuKey)
    expect(deriveActiveState(true, { annualVolume: 1, peakVolume: null, lifetimeVolume: null })).toBe('active')
  })

  it('a genuinely different variant (different dimension values) never collides with a reordered one', () => {
    const a = buildCompositeCanonicalKey({ vehicle: makeDimensionValue('Q71'), driveType: makeDimensionValue('RWD') })
    const b = buildCompositeCanonicalKey({ vehicle: makeDimensionValue('Q71'), driveType: makeDimensionValue('AWD') })
    expect(a).not.toBe(b)
  })

  it('across the shared-manufacturing fixture, matching two independently-built container snapshots by key recovers all 5 pairs', () => {
    const alt = buildSharedManufacturingFixture()
    const neu = buildSharedManufacturingFixture() // independent rebuild — simulates a re-parsed NEU file with identical business content.
    const altByKey = new Map(alt.activeVariants.map((v) => [v.compositeCanonicalKey, v]))
    const matched = neu.activeVariants.filter((v) => altByKey.has(v.compositeCanonicalKey))
    expect(matched).toHaveLength(5)
  })
})
