import { describe, it, expect } from 'vitest'
import { parseG60Workbook } from '../parser'
import { analyzeG60Pair, type G60Analysis } from '../analyze'
import {
  buildG60Buckets,
  buildG60Bridge,
  buildG60MoversInput,
  buildG60Anomalies,
  buildG60Levers,
  buildProdIndex,
} from '../view'
import { makeWorkbook } from './fixtures'

const basis = parseG60Workbook(makeWorkbook(100, 200))
const repr = parseG60Workbook(makeWorkbook(110, 260, 22, 33))
const a: G60Analysis = analyzeG60Pair(basis, repr)

describe('buildG60Buckets', () => {
  it('maps all six V11 buckets ALT vs NEU with deltas', () => {
    const buckets = buildG60Buckets(a)
    expect(buckets.map((b) => b.key)).toEqual(['Material', 'Labor', 'Manufacturing', 'ScrapB', 'SGA', 'Profit'])
    const mat = buckets[0]
    expect(mat.alt).toBeCloseTo(a.prog.basis.Material)
    expect(mat.neu).toBeCloseTo(a.prog.repr.Material)
    expect(mat.delta).toBeCloseTo(a.prog.repr.Material - a.prog.basis.Material)
  })
})

describe('buildG60Bridge', () => {
  const bridge = buildG60Bridge(a)!

  it('anchors at total Sales and explains the delta via bucket steps + Other residual', () => {
    expect(bridge.start).toBeCloseTo(a.set_b)
    expect(bridge.end).toBeCloseTo(a.set_r)
    const sumSteps = bridge.steps.reduce((s, x) => s + x.delta, 0)
    expect(sumSteps).toBeCloseTo(bridge.delta, 6)
  })

  it('composes the NEU bar from the six buckets plus Other', () => {
    const total = bridge.endComposition.reduce((s, c) => s + c.value, 0)
    expect(total).toBeCloseTo(a.set_r, 6)
  })
})

describe('buildG60MoversInput', () => {
  it('creates one row per common tab from the sales deltas', () => {
    const input = buildG60MoversInput(a)
    expect(input.rows).toHaveLength(2)
    const first = input.rows.find((r) => r.stepLabel.startsWith('2_2'))!
    expect(first.delta).toBeCloseTo(a.salesByTab['2_2'].r - a.salesByTab['2_2'].b)
    expect(first.uncertain).toBe(false)
  })
})

describe('buildG60Anomalies', () => {
  it('maps driver diffs with CMEAN clear names and hot flag ≥20 %', () => {
    const analysis: G60Analysis = {
      ...a,
      driverDiff: [
        { code: 'C42', label: 'Lohnsatz DE', basis: 38, repr: 47.5, chg: 0.25 },
        { code: 'C99', label: 'Unbekannt', basis: 1, repr: 1.1, chg: 0.1 },
      ],
    }
    const rows = buildG60Anomalies(analysis)
    expect(rows[0]).toMatchObject({ code: 'C42', name: 'Lohnsatz Neustadt (DE)', category: 'lohn', hot: true })
    expect(rows[1]).toMatchObject({ code: 'C99', name: 'Unbekannt', category: 'sonst', hot: false })
    // sorted by |chg| descending
    expect(Math.abs(rows[0].chg ?? 0)).toBeGreaterThanOrEqual(Math.abs(rows[1].chg ?? 0))
  })
})

describe('buildG60Levers', () => {
  it('derives the three V11 lever texts from glob/ineff/driver diffs', () => {
    const levers = buildG60Levers({
      ...a,
      glob: 1.12,
      driverDiff: [{ code: 'C42', label: 'Lohnsatz DE', basis: 38, repr: 35, chg: -0.0789 }],
    })
    expect(levers).toHaveLength(3)
    expect(levers[0].title).toContain('×1.12')
    expect(levers[0].ask).toContain('offenlegen')
    expect(levers[1].ask.length).toBeGreaterThan(10)
    expect(levers[2].ask).toContain('durchreichen')
  })
})

describe('buildProdIndex', () => {
  it('indexes NEU against ALT=100 per production KPI (median over tabs)', () => {
    const idx = buildProdIndex(a)
    const mss = idx.find((r) => r.kpi === 'Maschinensatz')!
    expect(mss.alt).toBe(100)
    expect(mss.neu).toBeCloseTo((a.glob ?? 1) * 100, 5)
    // fixture: cycle unchanged → index 100
    expect(idx.find((r) => r.kpi === 'Cycle time')!.neu).toBeCloseTo(100)
  })
})
