// Loop 13: das AI-Paket und seine Wächter — Positiv, Negativ, Gegenprobe.
//
// FIXTURE-DATEN-REGEL: alle Werte sind freie Erfindung.

import { describe, expect, it } from 'vitest'
import {
  AI_PROMPT_VERSION,
  buildAiPackage,
  validateAiSummary,
  zahlenInAntwort,
} from '@/lib/qaf-differences/internal/ai-package'

function kanonisch(overrides: Record<string, unknown> = {}): string {
  return JSON.stringify({
    ai_ready: {
      safe_to_summarize: true,
      numeric_generation_by_ai_prohibited: true,
      sections: {},
    },
    facts: [
      {
        fact_id: 'FACT-001',
        statement: 'Der Angebotspreis stieg um 2,47 EUR je Stück.',
        values: [{ key: 'delta', value: 2.47, unit: 'EUR_per_piece' }],
        difference_ids: ['DIF-SUM-001'],
      },
    ],
    differences: [{ difference_id: 'DIF-SUM-001' }],
    findings: [],
    analysis_areas: {
      summary: { state: 'populated', applicability: 'required' },
      logistics: { state: 'not_computed', applicability: 'required' },
    },
    run_manifest: {
      canonical_schema_version: '1.0.0',
      comparison_engine_version: '9.9.9-test',
      canonical_content_hash: 'sha256:abc',
      source_hashes: { 'f-1': 'sha256:def' },
    },
    ...overrides,
  })
}

describe('buildAiPackage — hartes Gate', () => {
  it('baut das Paket nur aus dem kanonischen Dokument und trägt den versionierten Prompt', () => {
    const res = buildAiPackage(kanonisch())
    expect(res.ok).toBe(true)
    if (!res.ok) return
    expect(res.aiPackage.prompt.version).toBe(AI_PROMPT_VERSION)
    expect(res.aiPackage.prompt.system_de).toContain('Du rechnest nicht')
    expect(res.aiPackage.facts).toHaveLength(1)
    expect(res.aiPackage.analysis_areas.logistics.state).toBe('not_computed')
    expect(res.aiPackage.run_manifest.canonical_content_hash).toBe('sha256:abc')
  })

  it('verweigert bei safe_to_summarize=false und nennt die blockierenden Gründe (Gegenprobe)', () => {
    const res = buildAiPackage(
      kanonisch({
        ai_ready: {
          safe_to_summarize: false,
          blocking_reasons: ['Reconciliation offen: Brücke schließt nicht.'],
        },
      }),
    )
    expect(res.ok).toBe(false)
    if (res.ok) return
    expect(res.reasonsDe.join(' ')).toContain('gesperrt')
    expect(res.reasonsDe.join(' ')).toContain('Reconciliation offen')
  })

  it('verweigert bei unlesbarem Dokument und fehlender ai_ready-Fläche', () => {
    expect(buildAiPackage('kein json').ok).toBe(false)
    expect(buildAiPackage(JSON.stringify({ facts: [] })).ok).toBe(false)
  })

  it('verweigert, wenn eine E-Mail-Adresse ins Paket gelangen würde (PII-Filter)', () => {
    const res = buildAiPackage(
      kanonisch({
        facts: [
          {
            fact_id: 'FACT-001',
            statement: 'Rückfragen an max.mustermann@example.com.',
            values: [],
            difference_ids: [],
          },
        ],
      }),
    )
    expect(res.ok).toBe(false)
    if (res.ok) return
    expect(res.reasonsDe.join(' ')).toContain('E-Mail')
  })
})

describe('validateAiSummary — Antwort-Wächter', () => {
  const paket = buildAiPackage(kanonisch())

  it('akzeptiert eine Antwort, deren Zahlen als Fakt-Werte vorliegen', () => {
    expect(
      validateAiSummary('Der Angebotspreis stieg um 2,47 EUR je Stück (DIF-SUM-001).', paket),
    ).toHaveLength(0)
  })

  it('lehnt jede Zahl ab, die in keinem Fakt-Wert steht (Halluzinationstest)', () => {
    const probleme = validateAiSummary('Die Fracht verteuerte sich um 3,10 EUR.', paket)
    expect(probleme.some((p) => p.kind === 'number_not_in_facts')).toBe(true)
  })

  it('lehnt Entwarnung ab, solange ein Bereich not_computed ist', () => {
    const probleme = validateAiSummary('Bei der Logistik gibt es keine Auffälligkeiten.', paket)
    expect(probleme.some((p) => p.kind === 'all_clear_on_not_computed')).toBe(true)
  })

  it('erlaubt die ehrliche Formulierung „nicht geprüft"', () => {
    expect(
      validateAiSummary('Logistik wurde nicht geprüft, dazu ist keine Aussage zulässig.', paket),
    ).toHaveLength(0)
  })

  it('verwirft jede Antwort ohne freigegebenes Paket (Mutations-Probe am Gate)', () => {
    const gesperrt = buildAiPackage(kanonisch({ ai_ready: { safe_to_summarize: false } }))
    const probleme = validateAiSummary('Alles gut.', gesperrt)
    expect(probleme.some((p) => p.kind === 'summary_blocked')).toBe(true)
  })
})

describe('zahlenInAntwort — beide Schreibweisen', () => {
  it('liest de-DE und technische Zahlen', () => {
    expect(zahlenInAntwort('1.234,56 und 7.89 und 42')).toEqual([1234.56, 7.89, 42])
  })
})

describe('Review-Fixes #487 — Wächter-Härtung', () => {
  const paket = buildAiPackage(kanonisch())

  it('schließt den Kennungs-Bypass: erfundene Zahl hinter „M-48213" wird abgelehnt', () => {
    // Die frühere Muster-Ausnahme (Buchstabe-Zahl) hätte das durchgelassen —
    // jetzt werden nur die EXAKTEN Kennungs-Literale des Pakets entfernt.
    const probleme = validateAiSummary('Laut Materialnummer M-48213 gab es eine Abweichung.', paket)
    expect(probleme.some((p) => p.kind === 'number_not_in_facts')).toBe(true)
  })

  it('lässt die im Paket geführte Kennung weiterhin zu (DIF-SUM-001)', () => {
    expect(
      validateAiSummary('Der Angebotspreis stieg um 2,47 EUR je Stück (DIF-SUM-001).', paket),
    ).toHaveLength(0)
  })

  it('lehnt wahrheitsgemäße Datumsnennungen nicht mehr ab', () => {
    expect(
      validateAiSummary('Der Angebotspreis stieg um 2,47 EUR (DIF-SUM-001), Angebotsdatum 07.04.2025.', paket),
    ).toHaveLength(0)
  })

  it('deckt die Dezimal-Lesart einer Dreiergruppen-Punktzahl („1.234")', () => {
    const res = buildAiPackage(
      kanonisch({
        facts: [
          {
            fact_id: 'FACT-001',
            statement: 'Der Faktor beträgt 1,234.',
            values: [{ key: 'faktor', value: 1.234, unit: 'ratio' }],
            difference_ids: ['DIF-SUM-001'],
          },
        ],
      }),
    )
    expect(validateAiSummary('Der Faktor beträgt 1.234.', res)).toHaveLength(0)
    // Gegenprobe: ein plain-Integer ohne Punkt-Form bleibt ungedeckt.
    expect(
      validateAiSummary('Der Faktor beträgt 1234.', res).some((p) => p.kind === 'number_not_in_facts'),
    ).toBe(true)
  })

  it('verweigert NICHT wegen sha256-Hashes im Manifest (PII-Fehlalarm)', () => {
    const res = buildAiPackage(
      kanonisch({
        run_manifest: {
          canonical_schema_version: '1.0.0',
          comparison_engine_version: '9.9.9-test',
          canonical_content_hash: 'sha256:ac2823c8640415a22c63ac21a1e8f72158ccb778b295929bab00f00991535738',
          source_hashes: {
            'f-1': 'sha256:00991535738ac2823c8640415a22c63ac21a1e8f72158ccb778b295929bab00f',
          },
        },
      }),
    )
    expect(res.ok).toBe(true)
  })

  it('findet eine echte E-Mail weiterhin — auch mit der ReDoS-festen Regex', () => {
    const res = buildAiPackage(
      kanonisch({
        facts: [
          {
            fact_id: 'FACT-001',
            statement: 'Kontakt: maria.beispiel+qaf@lieferant-x.example.de',
            values: [],
            difference_ids: [],
          },
        ],
      }),
    )
    expect(res.ok).toBe(false)
  })

  it('trägt den Leitfaden aus der lib im Paket (Schema lässt ihn im Dokument nicht zu)', () => {
    expect(paket.ok).toBe(true)
    if (!paket.ok) return
    const guide = paket.aiPackage.reading_guide
    expect(guide.numberRule).toContain('nicht gebildet')
  })
})
