// A10 Effective Capacity (§8.3) — hand-calculated tests, incl. the
// double-counting prevention the brief explicitly requires a test for.
// FIXTURE-DATEN-REGEL: every number below is FREE INVENTION.

import { describe, expect, it } from 'vitest'
import { computeEffectiveCapacity, computeUtilization } from '../internal/capacity'

describe('computeEffectiveCapacity', () => {
  it('hand calc: a MEASURED capacityPerHour wins outright, unaffected by OEE (no double efficiency application)', () => {
    // theoretical (for reference only) = 3600/30 = 120/h; x0.8 OEE = 96/h derived.
    // measured capacityPerHour (100) must win, NOT the derived 96.
    const result = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 30, partsPerCycle: undefined, capacityPerHour: 100, oee: 80 })
    expect(result.measuredCapacityPerHour).toBe(100)
    expect(result.derivedCapacityPerHour).toBe(96)
    expect(result.effectiveCapacityPerHour).toBe(100)
    expect(result.effectiveCycleTimeSec).toBe(36) // 3600/100
  })

  it('hand calc: cycle time + parts/cycle + parallel units + OEE, all derived', () => {
    // cycleTimeSecPerPart = 60/2 = 30; theoretical = 3600/30 * 2 parallel = 240/h; x0.75 OEE = 180/h.
    const result = computeEffectiveCapacity(
      { id: 'p1', cycleTimeSec: 60, partsPerCycle: 2, capacityPerHour: undefined, oee: 75 },
      { numParallelUnits: 2 },
    )
    expect(result.derivedCapacityPerHour).toBe(180)
    expect(result.effectiveCapacityPerHour).toBe(180)
    expect(result.effectiveCycleTimeSec).toBe(20) // 3600/180
    expect(result.efficiencySource).toBe('oee')
    expect(result.efficiencyFactorUsed).toBe(0.75)
  })

  it('hand calc: availability x performance x quality multiply when OEE is absent', () => {
    // theoretical = 3600/36 = 100/h; factor = 0.8 x 0.9 x 1.0 = 0.72; derived = 72/h.
    const result = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined }, {
      availabilityPct: 80,
      performancePct: 90,
      qualityPct: 100,
    })
    expect(result.efficiencySource).toBe('availability-performance-quality')
    expect(result.efficiencyFactorUsed).toBeCloseTo(0.72)
    expect(result.derivedCapacityPerHour).toBeCloseTo(72)
  })

  it('a missing performance/quality factor defaults to 100% (documented assumption, not a fabricated measurement)', () => {
    const result = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined }, { availabilityPct: 80 })
    expect(result.efficiencyFactorUsed).toBeCloseTo(0.8)
    expect(result.explain.exclusions.some((e) => e.includes('100% angenommen'))).toBe(true)
  })

  it('DOUBLE-COUNTING PREVENTION: OEE wins outright over a standalone availability override on the SAME node — never multiplied together', () => {
    // theoretical = 3600/36 = 100/h. If availability (0.5) were ALSO applied on
    // top of OEE (0.5), derived would wrongly be 100*0.5*0.5=25. Correct: 50.
    const result = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: 50 }, { availabilityPct: 50 })
    expect(result.efficiencySource).toBe('oee')
    expect(result.efficiencyFactorUsed).toBe(0.5)
    expect(result.derivedCapacityPerHour).toBe(50)
    expect(result.derivedCapacityPerHour).not.toBe(25)
  })

  it('hand calc: neither OEE nor availability -> factor 1.0 (documented assumption), full theoretical capacity', () => {
    const result = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined })
    expect(result.efficiencySource).toBe('none')
    expect(result.efficiencyFactorUsed).toBe(1)
    expect(result.derivedCapacityPerHour).toBe(100) // 3600/36
    expect(result.explain.exclusions.some((e) => e.includes('kein bekannter Verlust'))).toBe(true)
  })

  it('missing partsPerCycle defaults to 1 part/cycle (documented default, not a "not computable")', () => {
    const result = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 72, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined })
    expect(result.derivedCapacityPerHour).toBe(50) // 3600/72
  })

  it('F4/F9 FIX: the "Teile/Zyklus fehlt" exclusion only appears when partsPerCycle is ACTUALLY missing/invalid, not unconditionally on every result', () => {
    const withPartsPerCycle = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 30, partsPerCycle: 2, capacityPerHour: undefined, oee: 95 })
    expect(withPartsPerCycle.explain.exclusions.some((e) => e.includes('Teile/Zyklus fehlt'))).toBe(false)

    const withoutPartsPerCycle = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 30, partsPerCycle: undefined, capacityPerHour: undefined, oee: 95 })
    expect(withoutPartsPerCycle.explain.exclusions.some((e) => e.includes('Teile/Zyklus fehlt'))).toBe(true)
  })

  it('a non-positive numParallelUnits override falls back to 1, never to a zero-capacity result', () => {
    const withZero = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined }, { numParallelUnits: 0 })
    const withoutOverride = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined })
    expect(withZero.derivedCapacityPerHour).toBe(withoutOverride.derivedCapacityPerHour)
  })

  it('F15 FIX: an invalid numParallelUnits override (<= 0) still falls back to 1, but now surfaces an exclusion instead of failing silently', () => {
    const invalid = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined }, { numParallelUnits: 0 })
    expect(invalid.derivedCapacityPerHour).toBe(100) // unchanged: still falls back to 1 parallel unit, 3600/36
    expect(invalid.explain.exclusions.some((e) => e.includes('Parallel-Einheiten-Override ungültig'))).toBe(true)

    const negative = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 36, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined }, { numParallelUnits: -3 })
    expect(negative.explain.exclusions.some((e) => e.includes('Parallel-Einheiten-Override ungültig'))).toBe(true)

    const valid = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: 60, partsPerCycle: 2, capacityPerHour: undefined, oee: 75 }, { numParallelUnits: 2 })
    expect(valid.explain.exclusions.some((e) => e.includes('Parallel-Einheiten-Override ungültig'))).toBe(false)
  })

  it('returns null (not 0) for both capacity figures when cycle time is missing and nothing is measured', () => {
    const result = computeEffectiveCapacity({ id: 'p1', cycleTimeSec: undefined, partsPerCycle: undefined, capacityPerHour: undefined, oee: undefined })
    expect(result.derivedCapacityPerHour).toBeNull()
    expect(result.effectiveCapacityPerHour).toBeNull()
    expect(result.effectiveCycleTimeSec).toBeNull()
  })
})

describe('computeUtilization', () => {
  it('hand calc: effective CT 120s vs takt 100s -> 120% utilization', () => {
    expect(computeUtilization(120, 100).utilizationPct).toBe(120)
  })

  it('hand calc: effective CT below takt -> under 100%', () => {
    expect(computeUtilization(80, 100).utilizationPct).toBe(80)
  })

  it('returns null when either input is missing or takt is 0', () => {
    expect(computeUtilization(null, 100).utilizationPct).toBeNull()
    expect(computeUtilization(100, null).utilizationPct).toBeNull()
    expect(computeUtilization(100, 0).utilizationPct).toBeNull()
  })
})
