// Tests for multi-qaf/serialization.ts (KAR-929 / Multi-QAF-Programm P1.1).
//
// Covers: JSON round-trip fidelity, versioning (modelVersion stamp +
// rejection of an unsupported version), and undefined-tolerant reads (a
// partial/legacy-shaped payload still deserializes into a fully-shaped
// container instead of throwing or carrying `undefined` fields).

import { describe, expect, it } from 'vitest'
import {
  MULTI_QAF_MODEL_VERSION,
  deserializeMultiQafContainer,
  deserializeVirtualQafVariants,
  serializeMultiQafContainer,
  serializeVirtualQafVariants,
} from '../serialization'
import type { MultiQafContainer, VirtualQafVariant } from '../types'

function emptyContainer(): MultiQafContainer {
  return {
    sourceWorkbook: { fileName: 'synthetic.xlsx', fileHash: 'abc123', sheetNames: ['Zusammenfassung'], hiddenSheetNames: [] },
    detectedTemplateFamily: 'm_qaf_2_0',
    multiQafVersion: 'M-QAF Version 2.0 ',
    underlyingQafVersion: '8.1',
    language: 'de',
    currencies: ['EUR', 'USD'],
    sharedMetadata: { supplier: { value: 'Acme', sourceCell: { sheet: 'Zusammenfassung', cell: 'C5', row: 4, column: 2 } } },
    variantDimensions: [{ key: 'vehicle', headerRow: 3, label: 'Fahrzeug', sheet: 'Zusammenfassung' }],
    activeVariants: [],
    inactiveVariants: [],
    auxiliaryScenarios: [],
    helperColumns: [],
    sharedMaterialMaster: [],
    sharedManufacturingProfiles: [],
    sharedToolingData: [],
    setupCostProfiles: [],
    variantProfileBindings: [],
    summaryAggregation: { perVariant: [], formulaLineageNotes: [] },
    templateFingerprint: { family: 'm_qaf_2_0', structuralHash: 'hash-1', variantColumnCount: 25, headerRowCount: 12, knownProfile: null, classification: 'known' },
    warnings: [],
    confidence: 0.8,
    summaryMoneyRowsByVariant: {
      'variant-1': {
        scrap: { amount: { value: 4.2, currency: 'EUR' }, sourceCell: { sheet: 'Zusammenfassung', cell: 'Q27', row: 26, column: 17 } },
        otherSurcharges: null,
        offerBasePrice: null,
        offerBasePriceInclAllocation: null,
        offerPrice: null,
      },
    },
  }
}

function virtualVariant(): VirtualQafVariant {
  return {
    variantId: 'v1',
    containerFingerprint: 'hash-1',
    definition: {
      stableInternalId: 'v1',
      originalColumn: 'Q',
      originalColumnIndex: 17,
      originalVariantNumber: '1',
      originalLabels: ['Q71', 'RWD'],
      normalizedLabels: ['q71', 'rwd'],
      compositeCanonicalKey: 'vehicle=q71|driveType=rwd',
      dimensions: {
        vehicle: { raw: 'Q71', normalized: 'q71', sourceCell: null },
        driveType: { raw: 'RWD', normalized: 'rwd', sourceCell: null },
      },
      annualVolume: 92000,
      peakVolume: null,
      lifetimeVolume: null,
      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: null,
      totalProductionCosts: null,
      toolingAndFixtureCost: null,
      setupCostAllocation: null,
      scrap: null,
      otherSurcharges: null,
      offerBasePrice: null,
      offerBasePriceInclAllocation: null,
      offerPrice: null,
    },
    warnings: [],
  }
}

describe('serializeMultiQafContainer / deserializeMultiQafContainer', () => {
  it('round-trips a fully-populated container byte-for-byte (deep equal)', () => {
    const original = emptyContainer()
    const json = serializeMultiQafContainer(original)
    const restored = deserializeMultiQafContainer(json)
    expect(restored).toEqual(original)
  })

  it('stamps the current MULTI_QAF_MODEL_VERSION into the serialized envelope', () => {
    const json = serializeMultiQafContainer(emptyContainer())
    const parsed = JSON.parse(json) as { modelVersion: number }
    expect(parsed.modelVersion).toBe(MULTI_QAF_MODEL_VERSION)
  })

  it('throws on an unsupported modelVersion rather than silently reinterpreting it', () => {
    const json = JSON.stringify({ modelVersion: 999, container: emptyContainer() })
    expect(() => deserializeMultiQafContainer(json)).toThrow(/modelVersion/i)
  })

  it('throws on structurally invalid JSON (not an object / missing container)', () => {
    expect(() => deserializeMultiQafContainer('null')).toThrow()
    expect(() => deserializeMultiQafContainer(JSON.stringify({ modelVersion: MULTI_QAF_MODEL_VERSION }))).toThrow()
  })

  it('is undefined-tolerant when reading a partial payload: missing arrays/records default to empty, not undefined', () => {
    const partial = {
      modelVersion: MULTI_QAF_MODEL_VERSION,
      container: {
        detectedTemplateFamily: 'unknown',
        // sourceWorkbook, currencies, activeVariants, etc. all omitted.
      },
    }
    const restored = deserializeMultiQafContainer(JSON.stringify(partial))
    expect(restored.sourceWorkbook).toEqual({ fileName: null, fileHash: null, sheetNames: [], hiddenSheetNames: [] })
    expect(restored.currencies).toEqual([])
    expect(restored.activeVariants).toEqual([])
    expect(restored.inactiveVariants).toEqual([])
    expect(restored.sharedMetadata).toEqual({})
    expect(restored.templateFingerprint).toEqual({
      family: 'unknown',
      structuralHash: null,
      variantColumnCount: 0,
      headerRowCount: 0,
      knownProfile: null,
      classification: null,
    })
    expect(restored.summaryAggregation).toEqual({ perVariant: [], formulaLineageNotes: [] })
    expect(restored.confidence).toBe(0)
    expect(restored.detectedTemplateFamily).toBe('unknown')
    // KAR-951 — additive field, same undefined-tolerant default as every
    // other record field.
    expect(restored.summaryMoneyRowsByVariant).toEqual({})
  })

  // KAR-929 adversarial review F1: a PRESENT-but-partially-shaped nested
  // object must also come back fully shaped, not verbatim — the bug this
  // guards was that only total absence of sourceWorkbook/templateFingerprint/
  // summaryAggregation was normalized; a partial nested payload (present key,
  // missing sibling fields) passed straight through and TypeError'd on the
  // first array/field access a caller made.
  it('field-by-field normalizes a present-but-partial sourceWorkbook instead of passing it through verbatim', () => {
    const partial = {
      modelVersion: MULTI_QAF_MODEL_VERSION,
      container: {
        // sourceWorkbook IS present, but only fileName is set — sheetNames/
        // hiddenSheetNames/fileHash are missing, not just `undefined`-valued.
        sourceWorkbook: { fileName: 'partial.xlsx' },
      },
    }
    const restored = deserializeMultiQafContainer(JSON.stringify(partial))
    expect(restored.sourceWorkbook).toEqual({
      fileName: 'partial.xlsx',
      fileHash: null,
      sheetNames: [],
      hiddenSheetNames: [],
    })
  })

  it('field-by-field normalizes a present-but-partial templateFingerprint instead of passing it through verbatim', () => {
    const partial = {
      modelVersion: MULTI_QAF_MODEL_VERSION,
      container: {
        // Only `family` set — structuralHash/variantColumnCount/
        // headerRowCount/knownProfile missing.
        templateFingerprint: { family: 'm_qaf_1_0' },
      },
    }
    const restored = deserializeMultiQafContainer(JSON.stringify(partial))
    expect(restored.templateFingerprint).toEqual({
      family: 'm_qaf_1_0',
      structuralHash: null,
      variantColumnCount: 0,
      headerRowCount: 0,
      knownProfile: null,
      classification: null,
    })
  })

  it('field-by-field normalizes a present-but-partial summaryAggregation instead of passing it through verbatim', () => {
    const partial = {
      modelVersion: MULTI_QAF_MODEL_VERSION,
      container: {
        // Only perVariant set — formulaLineageNotes missing.
        summaryAggregation: { perVariant: [] },
      },
    }
    const restored = deserializeMultiQafContainer(JSON.stringify(partial))
    expect(restored.summaryAggregation).toEqual({ perVariant: [], formulaLineageNotes: [] })
  })
})

describe('serializeVirtualQafVariants / deserializeVirtualQafVariants', () => {
  it('round-trips an array of virtual variants', () => {
    const originals = [virtualVariant()]
    const json = serializeVirtualQafVariants(originals)
    const restored = deserializeVirtualQafVariants(json)
    expect(restored).toEqual(originals)
  })

  it('round-trips an empty array', () => {
    const json = serializeVirtualQafVariants([])
    expect(deserializeVirtualQafVariants(json)).toEqual([])
  })

  it('throws on an unsupported modelVersion', () => {
    const json = JSON.stringify({ modelVersion: 42, variants: [] })
    expect(() => deserializeVirtualQafVariants(json)).toThrow(/modelVersion/i)
  })

  // KAR-929 adversarial review F3: a non-array `variants` payload must be
  // rejected, not silently coerced to `[]` — consistent with the sibling
  // envelope checks (modelVersion/missing key) which all reject rather than
  // reinterpret.
  it('throws when variants is present but not an array, instead of silently coercing to []', () => {
    const json = JSON.stringify({ modelVersion: MULTI_QAF_MODEL_VERSION, variants: { not: 'an array' } })
    expect(() => deserializeVirtualQafVariants(json)).toThrow(/must be an array/i)
  })
})
