// Tests for the Summary-mode Preis-Kalkulator bridge (KAR-960/P3).

import { describe, expect, it } from 'vitest'
import {
  calculatorParamsFromSummaryStep,
  summaryCalculatorStepOptions,
  summaryCalculatorStepLabels,
  EMPTY_SUMMARY_MASTER_RATES,
  type SummaryMasterRates,
} from '../calculator-bridge'
import type { QAFRow } from '../../../../qaf-parser'

function step(overrides: Partial<QAFRow>): QAFRow {
  return {
    positionsnummer: '10',
    teilebenennung: '',
    prozessbezeichnung: 'Spritzguss',
    bezeichnungAnlage: '',
    standort: '',
    beschaffungswaehrung: 'EUR',
    zykluszeit: null,
    teileProZyklus: null,
    anzahlMA: null,
    lohnkosten: null,
    lohnzuschlagssaetze: null,
    mss: null,
    ruestkosten: null,
    fek: null,
    rfgk: null,
    fk: null,
    angebotswaehrung: 'EUR',
    wechselkurs: null,
    anzahlProAngebotsteil: null,
    fkAW: null,
    ausschuss: null,
    ausschusskosten: null,
    ...overrides,
  }
}

describe('calculatorParamsFromSummaryStep', () => {
  it('returns null when neither a step nor any master rate contributes anything', () => {
    expect(calculatorParamsFromSummaryStep(null, EMPTY_SUMMARY_MASTER_RATES)).toBeNull()
  })

  it('populates process fields from the step, leaving materialPerUnit/inefficiency honestly null', () => {
    const s = step({ zykluszeit: 12, teileProZyklus: 2, anzahlMA: 1.5, lohnkosten: 32, mss: 45 })
    const params = calculatorParamsFromSummaryStep(s, EMPTY_SUMMARY_MASTER_RATES)
    expect(params).not.toBeNull()
    expect(params!.cycleSec).toBe(12)
    expect(params!.partsPerCycle).toBe(2)
    expect(params!.employees).toBe(1.5)
    expect(params!.labourRatePerHour).toBe(32)
    expect(params!.machineRatePerHour).toBe(45)
    expect(params!.materialPerUnit).toBeNull()
    expect(params!.inefficiency).toBeNull()
  })

  it('prefers the per-step scrap rate over the Summary master scrap rate (§21)', () => {
    const s = step({ ausschuss: 0.03 })
    const masterRates: SummaryMasterRates = { ...EMPTY_SUMMARY_MASTER_RATES, scrapRate: { value: 0.05, cell: 'H10', status: 'SINGLE' } }
    const params = calculatorParamsFromSummaryStep(s, masterRates)
    expect(params!.scrapRate).toBe(0.03)
  })

  it('falls back to the Summary master scrap rate when the step has none', () => {
    const s = step({ zykluszeit: 10, ausschuss: null })
    const masterRates: SummaryMasterRates = { ...EMPTY_SUMMARY_MASTER_RATES, scrapRate: { value: 0.05, cell: 'H10', status: 'SINGLE' } }
    const params = calculatorParamsFromSummaryStep(s, masterRates)
    expect(params!.scrapRate).toBe(0.05)
  })

  it('applies the Summary SG&A/profit master rate to both FK and MAT (documented assumption)', () => {
    const masterRates: SummaryMasterRates = {
      scrapRate: null,
      sgaRate: { value: 0.08, cell: 'H12', status: 'SINGLE' },
      profitRate: { value: 0.05, cell: 'H14', status: 'SINGLE' },
    }
    const params = calculatorParamsFromSummaryStep(null, masterRates)
    expect(params!.sgaFkRate).toBe(0.08)
    expect(params!.sgaMatRate).toBe(0.08)
    expect(params!.profitFkRate).toBe(0.05)
    expect(params!.profitMatRate).toBe(0.05)
  })

  it('carries per-field source cells for the Quelle-Tooltip', () => {
    const s = step({ zykluszeit: 12, sourceCells: { zykluszeit: 'Fertigungskosten!G12' } })
    const params = calculatorParamsFromSummaryStep(s, EMPTY_SUMMARY_MASTER_RATES)
    expect(params!.sources.cycleSec).toBe('Fertigungskosten!G12')
  })

  it('works with only master rates and no selected step (§22 partial population)', () => {
    const masterRates: SummaryMasterRates = { ...EMPTY_SUMMARY_MASTER_RATES, sgaRate: { value: 0.1, cell: 'H12', status: 'SINGLE' } }
    const params = calculatorParamsFromSummaryStep(null, masterRates)
    expect(params).not.toBeNull()
    expect(params!.cycleSec).toBeNull()
    expect(params!.sgaFkRate).toBe(0.1)
  })

  // KAR-960 review fix (PR #327 finding 2) — the FieldConflictStatus a
  // master rate resolved to (SINGLE/AGREEMENT/INCONSISTENT,
  // resolveAllFieldCandidates) must reach the Preis-Kalkulator side params,
  // not be silently dropped. Before the fix, SummaryMasterRates carried no
  // `.status` field at all, so an INCONSISTENT-resolved rate was
  // indistinguishable from an unambiguous one by the time it reached the
  // chip UI.
  it('propagates an INCONSISTENT master-rate status onto sgaFkRate/sgaMatRate', () => {
    const masterRates: SummaryMasterRates = {
      ...EMPTY_SUMMARY_MASTER_RATES,
      sgaRate: { value: 0.08, cell: 'H12', status: 'INCONSISTENT' },
    }
    const params = calculatorParamsFromSummaryStep(null, masterRates)
    expect(params!.statuses.sgaFkRate).toBe('INCONSISTENT')
    expect(params!.statuses.sgaMatRate).toBe('INCONSISTENT')
  })

  it('propagates an INCONSISTENT master-rate status onto profitFkRate/profitMatRate', () => {
    const masterRates: SummaryMasterRates = {
      ...EMPTY_SUMMARY_MASTER_RATES,
      profitRate: { value: 0.05, cell: 'H14', status: 'INCONSISTENT' },
    }
    const params = calculatorParamsFromSummaryStep(null, masterRates)
    expect(params!.statuses.profitFkRate).toBe('INCONSISTENT')
    expect(params!.statuses.profitMatRate).toBe('INCONSISTENT')
  })

  it('propagates an INCONSISTENT scrap-rate status only when scrap comes from the master rate, not the step', () => {
    const masterRates: SummaryMasterRates = {
      ...EMPTY_SUMMARY_MASTER_RATES,
      scrapRate: { value: 0.05, cell: 'H10', status: 'INCONSISTENT' },
    }
    const fromMaster = calculatorParamsFromSummaryStep(null, masterRates)
    expect(fromMaster!.statuses.scrapRate).toBe('INCONSISTENT')

    // §21: the step's OWN scrap value wins over the master rate — and since
    // it is a single raw cell (no label-collision concept), it must not
    // inherit the master rate's (unrelated) INCONSISTENT status.
    const s = step({ ausschuss: 0.03 })
    const fromStep = calculatorParamsFromSummaryStep(s, masterRates)
    expect(fromStep!.scrapRate).toBe(0.03)
    expect(fromStep!.statuses.scrapRate).toBeUndefined()
  })

  it('also carries a non-conflicting SINGLE status (not just INCONSISTENT) — statuses always reflects the real resolution', () => {
    const masterRates: SummaryMasterRates = { ...EMPTY_SUMMARY_MASTER_RATES, sgaRate: { value: 0.08, cell: 'H12', status: 'SINGLE' } }
    const params = calculatorParamsFromSummaryStep(null, masterRates)
    expect(params!.statuses.sgaFkRate).toBe('SINGLE')
  })

  it('leaves statuses empty for fields with no master-rate source (Fertigungskosten step fields have no conflict concept)', () => {
    const s = step({ zykluszeit: 12 })
    const params = calculatorParamsFromSummaryStep(s, EMPTY_SUMMARY_MASTER_RATES)
    expect(params!.statuses.cycleSec).toBeUndefined()
  })
})

describe('summaryCalculatorStepOptions', () => {
  it('lists distinct position numbers in first-seen order', () => {
    const options = summaryCalculatorStepOptions([
      { position_number: '10', process_name: 'A' },
      { position_number: '20', process_name: 'B' },
      { position_number: '10', process_name: 'A' },
    ])
    expect(options).toEqual(['10', '20'])
  })

  it('falls back to process_name when position_number is blank', () => {
    const options = summaryCalculatorStepOptions([{ position_number: null, process_name: 'Spritzguss' }])
    expect(options).toEqual(['Spritzguss'])
  })
})

// KAR-966 item 1: speaking Preis-Kalkulator dropdown label per step VALUE —
// kept separate from summaryCalculatorStepOptions's lookup-key contract.
describe('summaryCalculatorStepLabels', () => {
  it('formats "<process_name> (<position_number>)" when both are present and distinct', () => {
    const labels = summaryCalculatorStepLabels([{ position_number: '10', process_name: 'Spritzguss' }])
    expect(labels).toEqual({ '10': 'Spritzguss (10)' })
  })

  it('uses the bare process_name when position_number is blank (matches the VALUE summaryCalculatorStepOptions picked)', () => {
    const labels = summaryCalculatorStepLabels([{ position_number: null, process_name: 'Spritzguss' }])
    expect(labels).toEqual({ Spritzguss: 'Spritzguss' })
  })

  it('uses the bare position_number when process_name is blank', () => {
    const labels = summaryCalculatorStepLabels([{ position_number: '20', process_name: null }])
    expect(labels).toEqual({ '20': '20' })
  })

  it('does not double up when process_name equals position_number', () => {
    const labels = summaryCalculatorStepLabels([{ position_number: 'Montage', process_name: 'Montage' }])
    expect(labels).toEqual({ Montage: 'Montage' })
  })

  it('keys the label map by the SAME value summaryCalculatorStepOptions returns, so a caller can zip them 1:1', () => {
    const steps = [
      { position_number: '10', process_name: 'A' },
      { position_number: '20', process_name: 'B' },
      { position_number: '10', process_name: 'A' },
    ]
    const values = summaryCalculatorStepOptions(steps)
    const labels = summaryCalculatorStepLabels(steps)
    expect(Object.keys(labels).sort()).toEqual([...values].sort())
  })

  it('skips a step with neither field (no key to attach a label to)', () => {
    const labels = summaryCalculatorStepLabels([{ position_number: null, process_name: null }])
    expect(labels).toEqual({})
  })
})
