import { describe, it, expect } from 'vitest'
import type { QAFRow } from '@/lib/qaf-parser'
import { matchSteps } from '../matcher'
import type { StepMatch } from '../types'

function mkRow(overrides: Partial<QAFRow> = {}): QAFRow {
  return {
    positionsnummer: '1',
    teilebenennung: 'Gehaeuse',
    prozessbezeichnung: 'Montage',
    bezeichnungAnlage: 'Anlage A',
    standort: 'Werk 1',
    beschaffungswaehrung: 'EUR',
    angebotswaehrung: 'EUR',
    zykluszeit: 10,
    teileProZyklus: 1,
    anzahlMA: 1,
    lohnkosten: 30,
    lohnzuschlagssaetze: 10,
    mss: 50,
    ruestkosten: 0,
    fek: 0,
    rfgk: 0,
    fk: 100,
    wechselkurs: 1,
    anzahlProAngebotsteil: 1,
    fkAW: 100,
    ausschuss: 2,
    ausschusskosten: 1,
    ...overrides,
  }
}

function find(matches: StepMatch[], altIndex: number | null, neuIndex: number | null): StepMatch | undefined {
  return matches.find((m) => m.altIndex === altIndex && m.neuIndex === neuIndex)
}

describe('matchSteps — 5-stage cascade', () => {
  it('Stage 1: identical step → safe_match, no review', () => {
    const m = matchSteps([mkRow()], [mkRow()])
    const match = find(m, 0, 0)
    expect(match?.matchStatus).toBe('safe_match')
    expect(match?.requiresReview).toBe(false)
    expect(match?.confidenceScore).toBeGreaterThanOrEqual(0.99)
  })

  it('Stage 2: same position, similar process → probable_match', () => {
    // "Assembly" vs "Assembly / Entfall gAMS" — real B3 example // allow-customer-string
    const alt = [mkRow({ prozessbezeichnung: 'Assembly' })] // allow-customer-string
    const neu = [mkRow({ prozessbezeichnung: 'Assembly / Entfall gAMS' })] // allow-customer-string
    const match = find(matchSteps(alt, neu), 0, 0)
    expect(match?.matchStatus).toBe('probable_match')
  })

  it('Stage 2: probable match flags review when cost diverges strongly', () => {
    const alt = [mkRow({ prozessbezeichnung: 'Schweissen', fk: 100 })]
    const neu = [mkRow({ prozessbezeichnung: 'Schweissen MAG', fk: 200 })] // +100 % FK
    const match = find(matchSteps(alt, neu), 0, 0)
    expect(match?.matchStatus).toBe('probable_match')
    expect(match?.requiresReview).toBe(true)
  })

  it('Stage 3: same position, both names strongly divergent → possible_structure_change + review', () => {
    const alt = [mkRow({ prozessbezeichnung: 'Montage', bezeichnungAnlage: 'Roboter R1' })]
    const neu = [mkRow({ prozessbezeichnung: 'Lackieren', bezeichnungAnlage: 'Kabine K9' })]
    const match = find(matchSteps(alt, neu), 0, 0)
    expect(match?.matchStatus).toBe('possible_structure_change')
    expect(match?.requiresReview).toBe(true)
  })

  it('Stage 4: no position equality but similar process+machine → candidate_match + review', () => {
    const alt = [mkRow({ positionsnummer: '5', prozessbezeichnung: 'CNC Fraesen', bezeichnungAnlage: 'DMG 1' })]
    const neu = [mkRow({ positionsnummer: '8', prozessbezeichnung: 'CNC Fraesen', bezeichnungAnlage: 'DMG 1' })]
    const match = find(matchSteps(alt, neu), 0, 0)
    expect(match?.matchStatus).toBe('candidate_match')
    expect(match?.requiresReview).toBe(true)
  })

  it('Stage 5: step only in NEU → new_step; only in ALT → removed_step', () => {
    const alt = [mkRow({ positionsnummer: '1', prozessbezeichnung: 'Montage' })]
    const neu = [
      mkRow({ positionsnummer: '1', prozessbezeichnung: 'Montage' }),
      mkRow({ positionsnummer: '2', prozessbezeichnung: 'Pruefen', bezeichnungAnlage: 'Pruefstand' }),
    ]
    const m = matchSteps(alt, neu)
    expect(find(m, 0, 0)?.matchStatus).toBe('safe_match')
    const added = m.find((x) => x.matchStatus === 'new_step')
    expect(added?.neuIndex).toBe(1)
    expect(added?.altIndex).toBeNull()
  })

  it('removed_step when a step disappears in NEU', () => {
    const alt = [
      mkRow({ positionsnummer: '1', prozessbezeichnung: 'Montage' }),
      mkRow({ positionsnummer: '2', prozessbezeichnung: 'Entgraten', bezeichnungAnlage: 'Buerste' }),
    ]
    const neu = [mkRow({ positionsnummer: '1', prozessbezeichnung: 'Montage' })]
    const removed = matchSteps(alt, neu).find((x) => x.matchStatus === 'removed_step')
    expect(removed?.altIndex).toBe(1)
    expect(removed?.neuIndex).toBeNull()
  })

  it('keeps position "2a" as text and matches it', () => {
    const alt = [mkRow({ positionsnummer: '2a', prozessbezeichnung: 'Kleben' })]
    const neu = [mkRow({ positionsnummer: '2a', prozessbezeichnung: 'Kleben' })]
    const match = find(matchSteps(alt, neu), 0, 0)
    expect(match?.matchStatus).toBe('safe_match')
  })

  it('never matches one alt step to two neu steps', () => {
    const alt = [mkRow({ positionsnummer: '1', prozessbezeichnung: 'Montage' })]
    const neu = [
      mkRow({ positionsnummer: '1', prozessbezeichnung: 'Montage' }),
      mkRow({ positionsnummer: '1', prozessbezeichnung: 'Montage' }),
    ]
    const m = matchSteps(alt, neu)
    const consumingAlt0 = m.filter((x) => x.altIndex === 0)
    expect(consumingAlt0).toHaveLength(1)
    // the second neu step must fall through to new_step
    expect(m.some((x) => x.matchStatus === 'new_step' && x.neuIndex === 1)).toBe(true)
  })

  it('covers every alt and neu index exactly once', () => {
    const alt = [mkRow({ positionsnummer: '1' }), mkRow({ positionsnummer: '2', prozessbezeichnung: 'X', bezeichnungAnlage: 'Y' })]
    const neu = [mkRow({ positionsnummer: '1' })]
    const m = matchSteps(alt, neu)
    const altCovered = new Set(m.filter((x) => x.altIndex !== null).map((x) => x.altIndex))
    const neuCovered = new Set(m.filter((x) => x.neuIndex !== null).map((x) => x.neuIndex))
    expect(altCovered).toEqual(new Set([0, 1]))
    expect(neuCovered).toEqual(new Set([0]))
  })
})
