import { describe, it, expect } from 'vitest'
import {
  computeBeforeAfterRows,
  computeTotals,
  type BeforeAfterInput,
  type BeforeAfterRow,
} from '../before-after'

function makeInput(overrides: Partial<BeforeAfterInput> = {}): BeforeAfterInput {
  return {
    stepId: 'step-1',
    stationName: 'Station A',
    stepNumber: 1,
    observed_ct_sec: 45,
    possible_ct_sec: 38,
    ...overrides,
  }
}

describe('computeBeforeAfterRows', () => {
  it('computes delta and percent for normal values', () => {
    const rows = computeBeforeAfterRows([makeInput()])
    expect(rows).toHaveLength(1)
    const row = rows[0] as BeforeAfterRow
    expect(row.delta_sec).toBeCloseTo(-7)
    expect(row.delta_pct).toBeCloseTo(-15.56, 1)
  })

  it('marks positive savings (possible < observed) as hasSaving = true', () => {
    const rows = computeBeforeAfterRows([makeInput({ observed_ct_sec: 50, possible_ct_sec: 40 })])
    expect(rows[0]?.hasSaving).toBe(true)
  })

  it('marks no saving when possible >= observed', () => {
    const rows = computeBeforeAfterRows([makeInput({ observed_ct_sec: 40, possible_ct_sec: 50 })])
    expect(rows[0]?.hasSaving).toBe(false)
  })

  it('marks no saving when delta is zero', () => {
    const rows = computeBeforeAfterRows([makeInput({ observed_ct_sec: 40, possible_ct_sec: 40 })])
    expect(rows[0]?.hasSaving).toBe(false)
    expect(rows[0]?.delta_sec).toBe(0)
  })

  it('returns null for delta when possible_ct_sec is null', () => {
    const rows = computeBeforeAfterRows([makeInput({ possible_ct_sec: null })])
    expect(rows[0]?.delta_sec).toBeNull()
    expect(rows[0]?.delta_pct).toBeNull()
    expect(rows[0]?.hasSaving).toBe(false)
  })

  it('returns null for delta when observed_ct_sec is null', () => {
    const rows = computeBeforeAfterRows([makeInput({ observed_ct_sec: null })])
    expect(rows[0]?.delta_sec).toBeNull()
    expect(rows[0]?.delta_pct).toBeNull()
  })

  it('rounds delta_pct to 2 decimal places', () => {
    const rows = computeBeforeAfterRows([makeInput({ observed_ct_sec: 3, possible_ct_sec: 2 })])
    // delta = -1, delta_pct = -33.33...
    expect(rows[0]?.delta_pct).toBeCloseTo(-33.33, 1)
  })

  it('handles zero observed_ct_sec without division by zero', () => {
    const rows = computeBeforeAfterRows([makeInput({ observed_ct_sec: 0, possible_ct_sec: 0 })])
    expect(rows[0]?.delta_pct).toBeNull()
  })

  it('preserves step metadata', () => {
    const rows = computeBeforeAfterRows([makeInput({ stationName: 'Press 7', stepNumber: 3 })])
    expect(rows[0]?.stationName).toBe('Press 7')
    expect(rows[0]?.stepNumber).toBe(3)
  })
})

describe('computeTotals', () => {
  it('sums observed and possible CTs, computes overall delta', () => {
    const inputs: BeforeAfterInput[] = [
      makeInput({ observed_ct_sec: 40, possible_ct_sec: 30 }),
      makeInput({ stepId: 'step-2', stationName: 'B', stepNumber: 2, observed_ct_sec: 60, possible_ct_sec: 50 }),
    ]
    const rows = computeBeforeAfterRows(inputs)
    const totals = computeTotals(rows)
    expect(totals.totalObserved).toBeCloseTo(100)
    expect(totals.totalPossible).toBeCloseTo(80)
    expect(totals.totalDelta).toBeCloseTo(-20)
    expect(totals.totalDeltaPct).toBeCloseTo(-20)
  })

  it('ignores rows where possible_ct_sec is null in sums', () => {
    const inputs: BeforeAfterInput[] = [
      makeInput({ observed_ct_sec: 40, possible_ct_sec: 30 }),
      makeInput({ stepId: 'step-2', stationName: 'B', stepNumber: 2, observed_ct_sec: 60, possible_ct_sec: null }),
    ]
    const rows = computeBeforeAfterRows(inputs)
    const totals = computeTotals(rows)
    expect(totals.totalObserved).toBeCloseTo(40)
    expect(totals.totalPossible).toBeCloseTo(30)
  })

  it('returns null delta when no observed values exist', () => {
    const inputs: BeforeAfterInput[] = [
      makeInput({ observed_ct_sec: null, possible_ct_sec: null }),
    ]
    const rows = computeBeforeAfterRows(inputs)
    const totals = computeTotals(rows)
    expect(totals.totalObserved).toBeNull()
    expect(totals.totalDelta).toBeNull()
  })
})
