// Engine-Konfiguration (P1.5/KAR-896) — zentrale, versionierte Regel-Governance.
import { describe, it, expect } from 'vitest'
import {
  DEFAULT_ENGINE_CONFIG,
  validateEngineConfig,
  buildEngineConfigDelta,
  isEngineConfigVersionAtLeast,
  type EngineConfig,
} from '../engine-config'
import { RULE_ENGINE_CONFIG } from '../rule-engine'
import { RECONCILIATION_CONFIG } from '../reconciliation'
import { G60_STRUCTURE_GUARD_CONFIG } from '../g60/structure-guard'
import { DEFAULT_DIFFER_BANDS_CONFIG } from '../differ'
import { DEFAULT_MATCH_CONFIG } from '../types'
import { FORMULA_ENGINE_CONFIG } from '../formula-engine'
import { BUSINESS_RULES_CONFIG } from '../business-rules'
import { DEFAULT_MULTI_QAF_DETECTION_CONFIG } from '../qaf-type-detector'
import { MAKE_OR_BUY_INDICATION_CONFIG } from '../plausibility'

describe('DEFAULT_ENGINE_CONFIG', () => {
  it('assembles every section verbatim from its owning module default (no drift, no redefinition)', () => {
    expect(DEFAULT_ENGINE_CONFIG.ruleEngine).toBe(RULE_ENGINE_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.reconciliation).toBe(RECONCILIATION_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.g60StructureGuard).toBe(G60_STRUCTURE_GUARD_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.differBands).toBe(DEFAULT_DIFFER_BANDS_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.matching).toBe(DEFAULT_MATCH_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.formulaEngine).toBe(FORMULA_ENGINE_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.businessRules).toBe(BUSINESS_RULES_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.multiQafDetection).toBe(DEFAULT_MULTI_QAF_DETECTION_CONFIG)
    expect(DEFAULT_ENGINE_CONFIG.makeOrBuyIndication).toBe(MAKE_OR_BUY_INDICATION_CONFIG)
  })

  it('carries a non-empty configVersion stamp (1.5.0 — D17 adds the makeOrBuyIndication section)', () => {
    expect(DEFAULT_ENGINE_CONFIG.configVersion).toBe('1.5.0')
  })

  it('defaults multiQafDetection.enabled to true (KAR-925 Flag-ON, Kais-Go TG 8475 — see module header)', () => {
    expect(DEFAULT_ENGINE_CONFIG.multiQafDetection.enabled).toBe(true)
  })

  it('is itself valid (no violations)', () => {
    expect(validateEngineConfig(DEFAULT_ENGINE_CONFIG)).toEqual([])
  })
})

describe('validateEngineConfig', () => {
  function withOverride(patch: Partial<EngineConfig>): EngineConfig {
    return { ...DEFAULT_ENGINE_CONFIG, ...patch }
  }

  it('rejects an empty configVersion', () => {
    expect(validateEngineConfig(withOverride({ configVersion: '' }))).toContain('configVersion darf nicht leer sein.')
  })

  it('rejects a ruleEnforcement value outside warn/block', () => {
    const errors = validateEngineConfig(
      withOverride({ ruleEngine: { ruleEnforcement: 'yolo' as unknown as 'warn' } }),
    )
    expect(errors.some((e) => e.includes("ruleEngine.ruleEnforcement"))).toBe(true)
  })

  it('accepts both valid ruleEnforcement values', () => {
    expect(validateEngineConfig(withOverride({ ruleEngine: { ruleEnforcement: 'warn' } }))).toEqual([])
    expect(validateEngineConfig(withOverride({ ruleEngine: { ruleEnforcement: 'block' } }))).toEqual([])
  })

  it('rejects a non-positive relative reconciliation tolerance', () => {
    const errors = validateEngineConfig(
      withOverride({ reconciliation: { relativeTolerance: 0, absoluteToleranceMinor: 1 } }),
    )
    expect(errors).toContain('reconciliation.relativeTolerance muss positiv sein.')
  })

  it('rejects a negative absolute reconciliation tolerance', () => {
    const errors = validateEngineConfig(
      withOverride({ reconciliation: { relativeTolerance: 0.005, absoluteToleranceMinor: -1 } }),
    )
    expect(errors).toContain('reconciliation.absoluteToleranceMinor darf nicht negativ sein.')
  })

  it('accepts an absolute reconciliation tolerance of exactly 0 (a valid floor)', () => {
    const errors = validateEngineConfig(
      withOverride({ reconciliation: { relativeTolerance: 0.005, absoluteToleranceMinor: 0 } }),
    )
    expect(errors).toEqual([])
  })

  it('rejects g60StructureGuard.softConfidence outside (0, 1]', () => {
    expect(
      validateEngineConfig(withOverride({ g60StructureGuard: { softConfidence: 0, hardMismatchThreshold: 2 } })),
    ).toContain('g60StructureGuard.softConfidence muss in (0, 1] liegen.')
    expect(
      validateEngineConfig(withOverride({ g60StructureGuard: { softConfidence: 1.5, hardMismatchThreshold: 2 } })),
    ).toContain('g60StructureGuard.softConfidence muss in (0, 1] liegen.')
  })

  it('rejects a non-integer or sub-1 hardMismatchThreshold', () => {
    expect(
      validateEngineConfig(withOverride({ g60StructureGuard: { softConfidence: 0.6, hardMismatchThreshold: 1.5 } })),
    ).toContain('g60StructureGuard.hardMismatchThreshold muss eine ganze Zahl >= 1 sein.')
    expect(
      validateEngineConfig(withOverride({ g60StructureGuard: { softConfidence: 0.6, hardMismatchThreshold: 0 } })),
    ).toContain('g60StructureGuard.hardMismatchThreshold muss eine ganze Zahl >= 1 sein.')
  })

  it('accepts a g60StructureGuard config with no relocatedConfidence field at all (pre-P1.3 shape)', () => {
    expect(validateEngineConfig(withOverride({ g60StructureGuard: { softConfidence: 0.6, hardMismatchThreshold: 2 } }))).toEqual([])
  })

  it('rejects a g60StructureGuard.relocatedConfidence outside (0, 1] when explicitly set', () => {
    expect(
      validateEngineConfig(
        withOverride({ g60StructureGuard: { softConfidence: 0.6, hardMismatchThreshold: 2, relocatedConfidence: 0 } }),
      ),
    ).toContain('g60StructureGuard.relocatedConfidence muss in (0, 1] liegen.')
    expect(
      validateEngineConfig(
        withOverride({ g60StructureGuard: { softConfidence: 0.6, hardMismatchThreshold: 2, relocatedConfidence: 1.1 } }),
      ),
    ).toContain('g60StructureGuard.relocatedConfidence muss in (0, 1] liegen.')
  })

  it('accepts a valid explicit g60StructureGuard.relocatedConfidence', () => {
    expect(
      validateEngineConfig(
        withOverride({ g60StructureGuard: { softConfidence: 0.6, hardMismatchThreshold: 2, relocatedConfidence: 0.8 } }),
      ),
    ).toEqual([])
  })

  it('rejects non-ascending differBands', () => {
    const errors = validateEngineConfig(
      withOverride({ differBands: { auffaellig10: 0.3, auffaellig25: 0.25, kritisch50: 0.5 } }),
    )
    expect(errors).toContain('differBands müssen strikt aufsteigend sein (auffaellig10 < auffaellig25 < kritisch50).')
  })

  it('rejects a non-positive differBands value', () => {
    const errors = validateEngineConfig(
      withOverride({ differBands: { auffaellig10: 0, auffaellig25: 0.25, kritisch50: 0.5 } }),
    )
    expect(errors).toContain('differBands.auffaellig10 muss positiv sein.')
  })

  it('rejects a matching threshold outside [0, 1]', () => {
    const errors = validateEngineConfig(
      withOverride({
        matching: { similarityThreshold: 1.2, candidateThreshold: 0.7, costDivergenceReviewThreshold: 0.25 },
      }),
    )
    expect(errors.some((e) => e.includes('matching.similarityThreshold'))).toBe(true)
  })

  it('accepts matching thresholds at the boundary (0 and 1)', () => {
    const errors = validateEngineConfig(
      withOverride({
        matching: { similarityThreshold: 0, candidateThreshold: 1, costDivergenceReviewThreshold: 0.5 },
      }),
    )
    expect(errors).toEqual([])
  })

  it('rejects a non-boolean formulaEngine.enabled', () => {
    const errors = validateEngineConfig(
      withOverride({ formulaEngine: { enabled: 'yes' as unknown as boolean } }),
    )
    expect(errors).toContain('formulaEngine.enabled muss ein boolean sein.')
  })

  it('accepts formulaEngine.enabled true and false', () => {
    expect(validateEngineConfig(withOverride({ formulaEngine: { enabled: true } }))).toEqual([])
    expect(validateEngineConfig(withOverride({ formulaEngine: { enabled: false } }))).toEqual([])
  })

  it('rejects a non-boolean multiQafDetection.enabled', () => {
    const errors = validateEngineConfig(
      withOverride({ multiQafDetection: { ...DEFAULT_MULTI_QAF_DETECTION_CONFIG, enabled: 'yes' as unknown as boolean } }),
    )
    expect(errors).toContain('multiQafDetection.enabled muss ein boolean sein.')
  })

  it('accepts multiQafDetection.enabled true and false', () => {
    expect(
      validateEngineConfig(withOverride({ multiQafDetection: { ...DEFAULT_MULTI_QAF_DETECTION_CONFIG, enabled: true } })),
    ).toEqual([])
    expect(
      validateEngineConfig(withOverride({ multiQafDetection: { ...DEFAULT_MULTI_QAF_DETECTION_CONFIG, enabled: false } })),
    ).toEqual([])
  })

  it('rejects a negative multiQafDetection threshold', () => {
    const errors = validateEngineConfig(
      withOverride({
        multiQafDetection: { ...DEFAULT_MULTI_QAF_DETECTION_CONFIG, strongVariantColumnPopulation: -1 },
      }),
    )
    expect(errors).toContain('multiQafDetection.strongVariantColumnPopulation darf nicht negativ sein.')
  })

  it('rejects a weak threshold above its strong counterpart', () => {
    const errors = validateEngineConfig(
      withOverride({
        multiQafDetection: { ...DEFAULT_MULTI_QAF_DETECTION_CONFIG, weakVariantColumnPopulation: 99 },
      }),
    )
    expect(errors).toContain(
      'multiQafDetection.weakVariantColumnPopulation darf nicht größer als strongVariantColumnPopulation sein.',
    )
  })

  it('rejects a non-boolean makeOrBuyIndication.enabled', () => {
    const errors = validateEngineConfig(
      withOverride({
        makeOrBuyIndication: { ...MAKE_OR_BUY_INDICATION_CONFIG, enabled: 'yes' as unknown as boolean },
      }),
    )
    expect(errors).toContain('makeOrBuyIndication.enabled muss ein boolean sein.')
  })

  it('rejects a non-positive makeOrBuyIndication threshold', () => {
    const errors = validateEngineConfig(
      withOverride({
        makeOrBuyIndication: { ...MAKE_OR_BUY_INDICATION_CONFIG, shareShiftPercentagePoints: 0 },
      }),
    )
    expect(errors).toContain('makeOrBuyIndication.shareShiftPercentagePoints muss positiv sein.')
  })
})

describe('buildEngineConfigDelta', () => {
  it('produces no overrides when effective === base (only a configVersion stamp)', () => {
    expect(buildEngineConfigDelta(DEFAULT_ENGINE_CONFIG)).toEqual({ configVersion: '1.5.0' })
  })

  it('includes only the changed section, not the whole object', () => {
    const effective: EngineConfig = {
      ...DEFAULT_ENGINE_CONFIG,
      reconciliation: { relativeTolerance: 0.01, absoluteToleranceMinor: 2 },
    }
    const delta = buildEngineConfigDelta(effective)
    expect(delta.overrides).toEqual({ reconciliation: { relativeTolerance: 0.01, absoluteToleranceMinor: 2 } })
    expect(delta.overrides).not.toHaveProperty('ruleEngine')
    expect(delta.overrides).not.toHaveProperty('matching')
  })

  it('includes multiple changed sections', () => {
    const effective: EngineConfig = {
      ...DEFAULT_ENGINE_CONFIG,
      ruleEngine: { ruleEnforcement: 'block' },
      differBands: { auffaellig10: 0.05, auffaellig25: 0.15, kritisch50: 0.3 },
    }
    const delta = buildEngineConfigDelta(effective)
    expect(Object.keys(delta.overrides ?? {}).sort()).toEqual(['differBands', 'ruleEngine'])
  })

  it('carries the effective configVersion, not the base one', () => {
    const effective: EngineConfig = { ...DEFAULT_ENGINE_CONFIG, configVersion: '1.1.0' }
    expect(buildEngineConfigDelta(effective).configVersion).toBe('1.1.0')
  })
})

// KAR-925 adversarial-review F1 fix (13.07.2026) — see
// resolveReplaceMultiQafDetectionConfig (rehydrate.ts), which uses this
// comparator to decide whether a comparison's persisted configVersion stamp
// predates the multiQafDetection.enabled flip.
describe('isEngineConfigVersionAtLeast', () => {
  it('true when version equals min', () => {
    expect(isEngineConfigVersionAtLeast('1.4.0', '1.4.0')).toBe(true)
  })

  it('true when version is greater (major/minor/patch, each checked in order)', () => {
    expect(isEngineConfigVersionAtLeast('1.4.1', '1.4.0')).toBe(true)
    expect(isEngineConfigVersionAtLeast('1.5.0', '1.4.0')).toBe(true)
    expect(isEngineConfigVersionAtLeast('2.0.0', '1.4.0')).toBe(true)
    // Component-wise, not lexicographic string comparison — '1.10.0' must
    // beat '1.9.0' despite '1.1...' < '1.9...' as raw strings.
    expect(isEngineConfigVersionAtLeast('1.10.0', '1.9.0')).toBe(true)
  })

  it('false when version is smaller', () => {
    expect(isEngineConfigVersionAtLeast('1.3.0', '1.4.0')).toBe(false)
    expect(isEngineConfigVersionAtLeast('1.0.0', '1.4.0')).toBe(false)
    expect(isEngineConfigVersionAtLeast('0.9.9', '1.4.0')).toBe(false)
  })

  it('false (never "assume current") for missing/unparseable version — never throws', () => {
    expect(isEngineConfigVersionAtLeast(undefined, '1.4.0')).toBe(false)
    expect(isEngineConfigVersionAtLeast('', '1.4.0')).toBe(false)
    expect(isEngineConfigVersionAtLeast('garbage', '1.4.0')).toBe(false)
    expect(isEngineConfigVersionAtLeast('1.4', '1.4.0')).toBe(false)
    expect(isEngineConfigVersionAtLeast('1.4.0.0', '1.4.0')).toBe(false)
  })
})
