// A3 Customer Takt (§8.1/§8.2) — hand-calculated tests.
// FIXTURE-DATEN-REGEL: every number below is FREE INVENTION.

import { describe, expect, it } from 'vitest'
import { computeNetAvailableTimePerDay, computeTakt, type ShiftModelInput } from '../internal/work-time'

describe('computeNetAvailableTimePerDay', () => {
  it('hand calc: 8h/shift, 2 shifts/day, 30min break -> 27000s/shift, 54000s/day', () => {
    // 8h = 28800s; -30min break (1800s) = 27000s/shift; x2 shifts = 54000s/day.
    const shiftModel: ShiftModelInput = { hoursPerShift: 8, shiftsPerDay: 2, breakMinPerShift: 30 }
    const result = computeNetAvailableTimePerDay(shiftModel)
    expect(result.netSecPerShift).toBe(27000)
    expect(result.netSecPerDay).toBe(54000)
  })

  it('hand calc: planned downtime is subtracted in addition to breaks', () => {
    // 8h=28800s; -30min break (1800s); -20min downtime (1200s) = 25800s/shift.
    const shiftModel: ShiftModelInput = { hoursPerShift: 8, shiftsPerDay: 1, breakMinPerShift: 30, plannedDowntimeMinPerShift: 20 }
    const result = computeNetAvailableTimePerDay(shiftModel)
    expect(result.netSecPerShift).toBe(25800)
    expect(result.netSecPerDay).toBe(25800)
  })

  it('flags (but does not fabricate) an omitted plannedDowntimeMinPerShift in explain.exclusions', () => {
    const result = computeNetAvailableTimePerDay({ hoursPerShift: 8, shiftsPerDay: 1, breakMinPerShift: 30 })
    expect(result.explain.exclusions.some((e) => e.includes('Kein geplanter Stillstand'))).toBe(true)
  })

  it('returns null (not a fabricated 0) when the shift model is undefined', () => {
    const result = computeNetAvailableTimePerDay(undefined)
    expect(result.netSecPerDay).toBeNull()
    expect(result.netSecPerShift).toBeNull()
  })

  it('returns null for an invalid shift model (hoursPerShift <= 0)', () => {
    const result = computeNetAvailableTimePerDay({ hoursPerShift: 0, shiftsPerDay: 1, breakMinPerShift: 0 })
    expect(result.netSecPerDay).toBeNull()
  })

  it('returns null (not a fabricated 0 or negative) when breaks/downtime consume the whole shift', () => {
    // 1h=3600s; -90min break (5400s) = -1800s -> invalid, not clamped to 0.
    const result = computeNetAvailableTimePerDay({ hoursPerShift: 1, shiftsPerDay: 1, breakMinPerShift: 90 })
    expect(result.netSecPerDay).toBeNull()
    expect(result.explain.exclusions.some((e) => e.includes('0 oder negativ'))).toBe(true)
  })
})

describe('computeTakt', () => {
  it('hand calc: 7.5h/shift, 1 shift/day, 30min break, 1 working day, demand 400 -> takt 63s', () => {
    // netSecPerShift = 7.5*3600 - 30*60 = 27000-1800 = 25200; x1 shift = 25200s/day; x1 day = 25200s total; /400 = 63s.
    const result = computeTakt({
      shiftModel: { hoursPerShift: 7.5, shiftsPerDay: 1, breakMinPerShift: 30 },
      workingDays: 1,
      demandQuantity: 400,
    })
    expect(result.netAvailableTimeSecPerDay).toBe(25200)
    expect(result.netAvailableTimeSecTotal).toBe(25200)
    expect(result.taktTimeSec).toBe(63)
  })

  it('hand calc: 8h/shift, 2 shifts/day, 45min break, 5 working days, demand 2000 -> takt 130.5s', () => {
    // netSecPerShift = 28800-2700 = 26100; x2 shifts = 52200s/day; x5 days = 261000s total; /2000 = 130.5s.
    const result = computeTakt({
      shiftModel: { hoursPerShift: 8, shiftsPerDay: 2, breakMinPerShift: 45 },
      workingDays: 5,
      demandQuantity: 2000,
    })
    expect(result.netAvailableTimeSecTotal).toBe(261000)
    expect(result.taktTimeSec).toBe(130.5)
  })

  it('returns null (not Infinity) when demandQuantity is 0', () => {
    const result = computeTakt({ shiftModel: { hoursPerShift: 8, shiftsPerDay: 1, breakMinPerShift: 0 }, workingDays: 1, demandQuantity: 0 })
    expect(result.taktTimeSec).toBeNull()
    expect(result.explain.exclusions.some((e) => e.includes('Bedarfsmenge ist 0'))).toBe(true)
  })

  it('returns null when workingDays <= 0', () => {
    const result = computeTakt({ shiftModel: { hoursPerShift: 8, shiftsPerDay: 1, breakMinPerShift: 0 }, workingDays: 0, demandQuantity: 100 })
    expect(result.taktTimeSec).toBeNull()
    expect(result.explain.exclusions.some((e) => e.includes('Arbeitstage'))).toBe(true)
  })

  it('returns null (not a fabricated value) when input is undefined', () => {
    const result = computeTakt(undefined)
    expect(result.taktTimeSec).toBeNull()
    expect(result.netAvailableTimeSecTotal).toBeNull()
  })

  it('propagates an invalid shift model as null takt (never silently substitutes a default)', () => {
    const result = computeTakt({ shiftModel: { hoursPerShift: -1, shiftsPerDay: 1, breakMinPerShift: 0 }, workingDays: 1, demandQuantity: 100 })
    expect(result.taktTimeSec).toBeNull()
    expect(result.netAvailableTimeSecPerDay).toBeNull()
  })

  it('carries a formula/dataBasis/exclusions explain object', () => {
    const explain = computeTakt(undefined).explain
    expect(explain.formula.length).toBeGreaterThan(0)
    expect(explain.dataBasis.length).toBeGreaterThan(0)
    expect(explain.exclusions.length).toBeGreaterThan(0)
  })
})
