// Werkzeugebene gegen eine anonymisierte Ableitung des Referenzfalls.
//
// Der Golden-Case-Test daneben braucht die echten Kalkulationen und übergeht
// sich deshalb überall ausser beim Betreiber — die CI hätte für diese Ebene
// sonst kein Gate. Diese Fixture ist strukturerhaltend pseudonymisiert und
// wertskaliert: Zählsuffixe, namensgleiche Wiederholungen, Zeilenlage und
// fehlende Auslegungsangaben sind dieselben wie im Original, nur eben ohne
// dessen Namen und Beträge.
//
// Erzeugt von build-anonymised-fixture.mjs / build-tooling-fixture.mjs
// (operator-lokal, neben den Echtdaten, nicht im Repo).

import { describe, it, expect } from 'vitest'
import { readFileSync } from 'node:fs'
import path from 'node:path'
import { analyseTooling, type MappableTool } from '../tooling-analysis'

interface ToolingFixture {
  scale: number
  toolsAward: MappableTool[]
  toolsCurrent: MappableTool[]
  expected: {
    positionsAward: number
    positionsCurrent: number
    newTools: number
    secondTools: number
    bridgeStart: number
    bridgeEnd: number
    bridgeDelta: number
    bridgeStepDeltas: number[]
    secondToolDeltas: number[]
  }
}

const FIXTURE: ToolingFixture = JSON.parse(
  readFileSync(path.join(__dirname, 'fixtures/tooling-anonymised.json'), 'utf8'),
)

const run = () =>
  analyseTooling(
    FIXTURE.toolsAward,
    FIXTURE.toolsCurrent,
    FIXTURE.expected.bridgeStart,
    FIXTURE.expected.bridgeEnd,
  )

const sorted = (xs: number[]) => [...xs].sort((a, b) => a - b)

describe('Werkzeugebene — anonymisierte Ableitung des Referenzfalls', () => {
  it('zählt die echten Positionen ohne die Summenzeilen des Formblatts', () => {
    const r = run()
    expect(r.counts.toolsAward).toBe(FIXTURE.expected.positionsAward)
    expect(r.counts.toolsCurrent).toBe(FIXTURE.expected.positionsCurrent)
  })

  it('findet die Zweitwerkzeuge mit ihren Beträgen', () => {
    const r = run()
    const zweit = r.mappings.filter((m) => m.classification === 'second_tool')
    expect(zweit).toHaveLength(FIXTURE.expected.secondTools)
    expect(sorted(zweit.map((m) => m.delta ?? 0))).toEqual(FIXTURE.expected.secondToolDeltas)
  })

  it('erkennt sowohl das durchnummerierte als auch das namensgleiche Zweitwerkzeug', () => {
    // Beide Formen kommen im Original vor. Fiele eine davon weg, bliebe die
    // Zahl oben trotzdem stehen — deshalb hier getrennt geprüft.
    const r = run()
    const zweit = r.mappings.filter((m) => m.classification === 'second_tool')
    expect(zweit.filter((m) => / 2$/.test(m.part)).length).toBeGreaterThan(0)
    expect(zweit.filter((m) => !/ 2$/.test(m.part)).length).toBeGreaterThan(0)
  })

  it('trennt neuen Umfang von Zweitwerkzeugen', () => {
    const r = run()
    expect(r.counts.byClassification.new_scope + r.counts.byClassification.second_tool).toBe(
      FIXTURE.expected.newTools,
    )
  })

  it('schliesst die Einmalzahlungs-Brücke ohne Restposten', () => {
    const r = run()
    expect(r.oneTimeBridge.delta).toBeCloseTo(FIXTURE.expected.bridgeDelta, 3)
    expect(r.oneTimeBridge.residual).toBeCloseTo(0, 3)
    expect(r.oneTimeBridge.passed).toBe(true)
  })

  it('führt jeden Betrag der Brücke auf seine Ursache zurück', () => {
    const r = run()
    expect(sorted(r.oneTimeBridge.steps.map((s) => s.delta)).map((d) => Number(d.toFixed(3)))).toEqual(
      FIXTURE.expected.bridgeStepDeltas.map((d) => Number(d.toFixed(3))),
    )
  })

  it('nennt jedes Zweitwerkzeug ohne Auslegungsnachweis', () => {
    // Der Befund, um den es geht: ein weiteres Werkzeug wird berechnet, womit es
    // ausgelegt ist steht nicht mehr da.
    const r = run()
    expect(r.counts.secondToolsWithoutSpec).toBe(FIXTURE.expected.secondTools)
  })

  it('findet die verschobene, wirtschaftlich unveränderte Zeile', () => {
    const r = run()
    const verschoben = r.mappings.filter((m) => m.rowMoved && m.classification === 'unchanged')
    expect(verschoben.length).toBeGreaterThan(0)
    expect(verschoben.every((m) => (m.delta ?? 0) === 0)).toBe(true)
  })

  it('meldet gelöschte Auslegungsangaben über den ganzen Stand', () => {
    const r = run()
    expect(r.counts.specRemoved).toBeGreaterThan(FIXTURE.expected.secondTools)
  })
})
