// Die `ai_ready`-Fläche (Kap. 17, R-21).

import { describe, it, expect } from 'vitest'
import {
  AI_READING_GUIDE,
  buildAiReady,
  buildFacts,
  factsFromLevers,
  sectionStates,
  validateAiReady,
  type FactInput,
  type ValidationCheck,
} from '../ai-ready'
import { assignDifferenceIds, type DifferenceRecord, type RawDifference } from '../all-differences'
import type { Lever } from '../negotiation-levers'

const diff = (over: Partial<RawDifference> = {}): RawDifference => ({
  area: 'SBM',
  kind: 'added',
  formulaFindingKind: null,
  descriptionDe: 'Zugang',
  valueAward: null,
  valueCurrent: 100,
  delta: 100,
  deltaNullReason: null,
  unit: 'EUR',
  cells: [{ fileRole: 'current', sheet: 'SBM-DEVICES-FWZ', cell: 'AP91', formula: null, valueState: 'constant' }],
  relatedDq: [],
  anchorRow: 91,
  ...over,
})

const records = (): DifferenceRecord[] =>
  assignDifferenceIds([diff({ anchorRow: 91, delta: 40_000 }), diff({ anchorRow: 97, delta: 20_000 })])

const fact = (over: Partial<FactInput> = {}): FactInput => ({
  statementDe: 'Zwei Werkzeuge über 60.000 EUR sind neu hinzugekommen.',
  values: [{ key: 'betrag', value: 60_000, unit: 'EUR' }],
  differenceIds: records().map((r) => r.differenceId),
  ...over,
})

describe('AI_READING_GUIDE', () => {
  it('erklärt Delta-Richtung, Nullbedeutung und Prozentskala', () => {
    // Was nicht in der Datei steht, kommt beim Modell nicht an.
    expect(AI_READING_GUIDE.deltaDefinition).toContain('minus')
    expect(AI_READING_GUIDE.nullSemantics).toContain('nicht berechenbar')
    expect(AI_READING_GUIDE.percentScale).toContain('0,31')
  })

  it('verbietet ausdrücklich das eigene Rechnen', () => {
    expect(AI_READING_GUIDE.numberRule).toContain('nicht gebildet')
  })

  it('trennt „geprüft und leer" von „nicht geprüft"', () => {
    expect(AI_READING_GUIDE.sectionStateRule).toContain('nicht geprüft')
  })
})

describe('buildFacts', () => {
  it('löst die Zellen aus dem Katalog auf', () => {
    const rs = records()
    const [f] = buildFacts([fact()], rs)
    expect(f.cells).toHaveLength(2)
    expect(f.factId).toBe('FACT-001')
    expect(f.confidence).toBe('deterministic')
  })

  it('bildet keinen Fakt, dessen Beleg fehlt', () => {
    // Lieber eine Aussage weniger als eine unbelegte.
    expect(buildFacts([fact({ differenceIds: ['D-SBM-999'] })], records())).toEqual([])
  })

  it('übernimmt die Kennzeichnung als Vermutung', () => {
    const [f] = buildFacts([fact({ confidence: 'candidate' })], records())
    expect(f.confidence).toBe('candidate')
  })
})

describe('factsFromLevers', () => {
  const lever = (over: Partial<Lever> = {}): Lever => ({
    leverId: 'LEV-001',
    rule: 'second_tool_without_spec',
    textDe: 'Zweitwerkzeuge über 60.000 EUR ohne Auslegungsnachweis (2 Positionen).',
    amount: 60_000,
    unit: 'EUR',
    severity: 'high',
    differenceIds: records().map((r) => r.differenceId),
    dqIds: [],
    ...over,
  })

  it('übernimmt Betrag und Anzahl als Werte, nicht nur als Text', () => {
    // Sonst müsste die Sprachschicht die Zahlen aus dem Satz herauslesen.
    const [f] = factsFromLevers([lever()], records())
    expect(f.values).toEqual([
      { key: 'betrag', value: 60_000, unit: 'EUR' },
      { key: 'positionen', value: 2, unit: 'pieces' },
    ])
  })

  it('kommt ohne Betrag aus, wo die Regel keinen kennt', () => {
    const [f] = factsFromLevers([lever({ amount: null, unit: null, textDe: 'Bei 2 Positionen fehlen Angaben.' })], records())
    expect(f.values).toEqual([{ key: 'positionen', value: 2, unit: 'pieces' }])
  })
})

describe('sectionStates', () => {
  it('unterscheidet geprüft-und-leer von nicht-geprüft', () => {
    // Der Fehler, der in einer Verhandlung am teuersten wird: „keine
    // Auffälligkeiten", wo ein Modul gar nicht gelaufen ist.
    const s = sectionStates({
      werkzeuge: { computed: true, factIds: ['FACT-001'] },
      material: { computed: true, factIds: [] },
      logistik: { computed: false, factIds: [] },
    })
    expect(s.werkzeuge.state).toBe('populated')
    expect(s.material.state).toBe('empty_verified')
    expect(s.logistik.state).toBe('not_computed')
  })

  it('leitet den Zustand nicht aus der Zahl der Fakten ab', () => {
    // Null Fakten sehen bei beiden gleich aus — der Unterschied steckt in
    // `computed`.
    const s = sectionStates({ a: { computed: false, factIds: [] }, b: { computed: true, factIds: [] } })
    expect(s.a.state).not.toBe(s.b.state)
  })
})

describe('buildAiReady', () => {
  const checks = (status: ValidationCheck['status']): ValidationCheck[] => [
    { checkId: 'bridge_closes', status },
    { checkId: 'all_values_sourced', status: 'pass' },
  ]

  it('gibt die Zusammenfassung frei, wenn alle Prüfungen bestehen', () => {
    const a = buildAiReady(buildFacts([fact()], records()), { werkzeuge: { computed: true, factIds: ['FACT-001'] } }, checks('pass'))
    expect(a.safeToSummarize).toBe(true)
    expect(a.blockingChecks).toEqual([])
  })

  it('sperrt die Zusammenfassung bei einer gescheiterten Prüfung, ohne die Fakten zu verstecken', () => {
    // Wegzulassen wäre schlechter: Eine fehlende Sektion sieht aus wie ein
    // sauberer Lauf.
    const a = buildAiReady(buildFacts([fact()], records()), {}, checks('fail'))
    expect(a.safeToSummarize).toBe(false)
    expect(a.blockingChecks).toEqual(['bridge_closes'])
    expect(a.facts).toHaveLength(1)
  })

  it('lässt nicht anwendbare Prüfungen nicht sperren', () => {
    const a = buildAiReady([], {}, checks('not_applicable'))
    expect(a.safeToSummarize).toBe(true)
  })
})

describe('validateAiReady', () => {
  const gueltig = () =>
    buildAiReady(buildFacts([fact()], records()), { werkzeuge: { computed: true, factIds: ['FACT-001'] } }, [])

  it('meldet nichts für eine geschlossene Fläche', () => {
    expect(validateAiReady(gueltig())).toEqual([])
  })

  it('meldet die Zahl, die im Satz steht, aber nicht in den Werten', () => {
    // Der unbequeme Punkt: Sonst muss die Sprachschicht sie aus dem Satz
    // herauslesen — und genau dabei entstehen Zahlendreher.
    const a = buildAiReady(
      buildFacts([fact({ statementDe: 'Zwei Werkzeuge über 61.000 EUR sind neu.' })], records()),
      {},
      [],
    )
    const p = validateAiReady(a)
    expect(p.map((x) => x.kind)).toContain('number_not_in_facts')
    expect(p[0].detail).toContain('61000')
  })

  it('lässt Zahlen durchgehen, die als Wert danebenstehen', () => {
    const a = buildAiReady(
      buildFacts(
        [
          fact({
            statementDe: '2 Positionen über 60.000 EUR.',
            values: [
              { key: 'betrag', value: 60_000, unit: 'EUR' },
              { key: 'positionen', value: 2, unit: 'pieces' },
            ],
          }),
        ],
        records(),
      ),
      {},
      [],
    )
    expect(validateAiReady(a)).toEqual([])
  })

  it('meldet den Fakt ohne Fundstelle', () => {
    const rs = assignDifferenceIds([diff({ cells: [] })])
    const a = buildAiReady(
      buildFacts([fact({ differenceIds: rs.map((r) => r.differenceId), values: [], statementDe: 'Ohne Beleg.' })], rs),
      {},
      [],
    )
    expect(validateAiReady(a).map((x) => x.kind)).toContain('fact_without_cells')
  })

  it('verlangt, dass eine Vermutung im Satz als solche erkennbar ist', () => {
    // Steht sie im Indikativ da, liest die Sprachschicht sie als Tatsache —
    // die Kennzeichnung im Feld daneben hilft dann niemandem.
    const a = buildAiReady(
      buildFacts([fact({ confidence: 'candidate', statementDe: 'Der Umfang wurde verlagert.', values: [] })], records()),
      {},
      [],
    )
    expect(validateAiReady(a).map((x) => x.kind)).toContain('unstated_candidate')
  })

  it('lässt eine im Konjunktiv formulierte Vermutung stehen', () => {
    const a = buildAiReady(
      buildFacts(
        [fact({ confidence: 'candidate', statementDe: 'Der Umfang könnte verlagert worden sein.', values: [] })],
        records(),
      ),
      {},
      [],
    )
    expect(validateAiReady(a)).toEqual([])
  })

  it('meldet den Verweis auf einen nicht geführten Fakt', () => {
    const a = buildAiReady(buildFacts([fact()], records()), { werkzeuge: { computed: true, factIds: ['FACT-999'] } }, [])
    expect(validateAiReady(a).map((x) => x.kind)).toContain('section_references_unknown_fact')
  })
})
