// Tests for multi-qaf/bridge.ts (KAR-929 / Multi-QAF-Programm P1.1) — the
// VirtualQafVariant <-> canonical-comparison-engine bridge.

import { describe, expect, it } from 'vitest'
import { SUMMARY_METRIC_KEYS } from '../../summary-metrics'
import { toCanonicalInputs, virtualVariantSummary, virtualVariantSummaryMetrics } from '../bridge'
import type { VirtualQafVariant } from '../types'

function baseVariant(overrides: Partial<VirtualQafVariant> = {}): VirtualQafVariant {
  return {
    variantId: 'v1',
    containerFingerprint: 'hash-1',
    definition: {
      stableInternalId: 'v1',
      originalColumn: 'Q',
      originalColumnIndex: 17,
      originalVariantNumber: '1',
      originalLabels: ['Q71 RWD LL'],
      normalizedLabels: ['q71 rwd ll'],
      compositeCanonicalKey: 'vehicle=q71|driveType=rwd|steeringSide=ll',
      dimensions: {
        vehicle: { raw: 'Q71', normalized: 'q71', sourceCell: null },
        driveType: { raw: 'RWD', normalized: 'rwd', sourceCell: null },
        steeringSide: { raw: 'LL', normalized: 'll', sourceCell: null },
        project: { raw: 'Nova', normalized: 'nova', sourceCell: null },
      },
      annualVolume: 92000,
      peakVolume: 150000,
      lifetimeVolume: 900000,
      currency: 'EUR',
      activeState: 'active',
      sourceReferences: [],
      confidence: 0.9,
    },
    materialRows: [],
    selectedManufacturingProfile: null,
    selectedSetupCostProfile: null,
    selectedToolingProfile: null,
    summaryTotals: {
      variantId: 'v1',
      materialCosts: { value: 100, currency: 'EUR' },
      materialCostsByCurrency: [{ value: 100, currency: 'EUR' }],
      manufacturingCosts: { value: 20, currency: 'EUR' },
      totalProductionCosts: { value: 120, currency: 'EUR' },
      toolingAndFixtureCost: { value: 5, currency: 'EUR' },
      setupCostAllocation: null,
      scrap: { value: 2, currency: 'EUR' },
      otherSurcharges: { value: 1, currency: 'EUR' },
      offerBasePrice: { value: 130, currency: 'EUR' },
      offerBasePriceInclAllocation: { value: 132, currency: 'EUR' },
      offerPrice: { value: 135, currency: 'EUR' },
    },
    warnings: [],
    ...overrides,
  }
}

describe('virtualVariantSummary', () => {
  it('maps compositeCanonicalKey onto partNumber (the primary comparison identity)', () => {
    const summary = virtualVariantSummary(baseVariant())
    expect(summary.partNumber.value).toBe('vehicle=q71|driveType=rwd|steeringSide=ll')
  })

  it('maps originalVariantNumber onto variant and the project dimension onto project', () => {
    const summary = virtualVariantSummary(baseVariant())
    expect(summary.variant.value).toBe('1')
    expect(summary.project.value).toBe('Nova')
  })

  // KAR-929 adversarial review F2c: an empty compositeCanonicalKey ('' — the
  // identity.ts "zero dimensions, no fallback" sentinel, e.g. two
  // identity-less reserved slots) must never be stamped through as a real
  // partNumber — two such variants would otherwise silently look like "the
  // same part" downstream.
  it('never stamps an empty compositeCanonicalKey through as partNumber — leaves it absent instead', () => {
    const v = baseVariant()
    const summary = virtualVariantSummary({
      ...v,
      definition: { ...v.definition, compositeCanonicalKey: '' },
    })
    expect(summary.partNumber.value).toBeNull()
  })

  it('maps peakVolume onto peakVolumeYear as a string', () => {
    const summary = virtualVariantSummary(baseVariant())
    expect(summary.peakVolumeYear.value).toBe('150000')
  })

  it('leaves peakVolumeYear null when peakVolume is null', () => {
    const v = baseVariant()
    const summary = virtualVariantSummary({
      ...v,
      definition: { ...v.definition, peakVolume: null },
    })
    expect(summary.peakVolumeYear.value).toBeNull()
  })

  it('leaves fields with no Multi-QAF-side source genuinely null (quotationDate, supplier, requestVersion, changeIndex, supplierNo, productionStartSop, deliverySite, shiftsPerWeek)', () => {
    const summary = virtualVariantSummary(baseVariant())
    expect(summary.quotationDate.value).toBeNull()
    expect(summary.supplier.value).toBeNull()
    expect(summary.requestVersion.value).toBeNull()
    expect(summary.changeIndex.value).toBeNull()
    expect(summary.supplierNo.value).toBeNull()
    expect(summary.productionStartSop.value).toBeNull()
    expect(summary.deliverySite.value).toBeNull()
    expect(summary.shiftsPerWeek.value).toBeNull()
  })

  it('every SummaryField carries cell: null (no single summary-sheet cell backs a virtual variant field)', () => {
    const summary = virtualVariantSummary(baseVariant())
    for (const field of Object.values(summary)) {
      expect(field.cell).toBeNull()
    }
  })
})

describe('virtualVariantSummaryMetrics', () => {
  it('maps the 8 metrics with a direct VirtualVariantSummaryTotals source', () => {
    const parse = virtualVariantSummaryMetrics(baseVariant())
    expect(parse.metrics.materialCosts.value).toBe(100)
    expect(parse.metrics.manufacturingCosts.value).toBe(20)
    expect(parse.metrics.totalProductionCosts.value).toBe(120)
    expect(parse.metrics.devicesAndTools.value).toBe(5)
    expect(parse.metrics.scrapMaterial.value).toBe(2)
    expect(parse.metrics.otherSurcharges.value).toBe(1)
    expect(parse.metrics.quotationBasePrice.value).toBe(130)
    expect(parse.metrics.quotationPrice.value).toBe(135)
  })

  it('KAR-951 F1: offerBasePriceInclAllocation is NOT mapped onto quotationBasePrice/quotationPrice — no canonical slot exists for it', () => {
    const parse = virtualVariantSummaryMetrics(baseVariant())
    // baseVariant() sets offerBasePriceInclAllocation to 132, distinct from
    // offerBasePrice (130) — quotationBasePrice must stay 130, proving the
    // two rows were never silently conflated (bridge.ts module header).
    expect(parse.metrics.quotationBasePrice.value).toBe(130)
    expect(parse.metrics.quotationPrice.value).toBe(135)
  })

  it('every SUMMARY_METRIC_KEYS entry is present in the returned metrics map (no missing keys)', () => {
    const parse = virtualVariantSummaryMetrics(baseVariant())
    for (const key of SUMMARY_METRIC_KEYS) {
      expect(parse.metrics[key]).toBeDefined()
    }
  })

  it('leaves canonical keys with no Multi-QAF-side source as absent (value null, confidence 0) rather than fabricating a value', () => {
    const parse = virtualVariantSummaryMetrics(baseVariant())
    for (const key of [
      'packagingTransportIncluded',
      'customsIncluded',
      'scrapManufacturing',
      'totalCosts',
      'rawMaterialPriceShareMaterial',
      'rawMaterialPriceShareEnergy',
      'customsSupplierToBMW',
      'transportSupplierToBMW',
      'oneTimeDevelopment',
      'oneTimeTools',
      'totalOneTimePayment',
      'costBreakdownAw1',
    ] as const) {
      expect(parse.metrics[key].value).toBeNull()
      expect(parse.metrics[key].confidence).toBe(0)
    }
  })

  it('carries the variant currency through to the parse-level currency field', () => {
    const parse = virtualVariantSummaryMetrics(baseVariant())
    expect(parse.currency).toBe('EUR')
  })

  it('leaves a mapped metric absent (not zero) when its VirtualVariantSummaryTotals source is null', () => {
    const v = baseVariant()
    const parse = virtualVariantSummaryMetrics({
      ...v,
      summaryTotals: { ...v.summaryTotals, manufacturingCosts: null },
    })
    expect(parse.metrics.manufacturingCosts.value).toBeNull()
    expect(parse.metrics.manufacturingCosts.confidence).toBe(0)
  })
})

describe('toCanonicalInputs', () => {
  it('populates summary and summaryMetrics', () => {
    const inputs = toCanonicalInputs(baseVariant())
    expect(inputs.summary).toBeDefined()
    expect(inputs.summary?.partNumber.value).toBe('vehicle=q71|driveType=rwd|steeringSide=ll')
    expect(inputs.summaryMetrics).toBeDefined()
    expect(inputs.summaryMetrics?.metrics.materialCosts.value).toBe(100)
  })

  it('intentionally omits ref and steps — never fabricates them (honest P1.1 scope)', () => {
    const inputs = toCanonicalInputs(baseVariant())
    expect(inputs.ref).toBeUndefined()
    expect(inputs.steps).toBeUndefined()
    expect(inputs.materialRows).toBeUndefined()
  })
})
