// loadComparisonComposition (Loop 6 Etappe 3): die EINE Lade+Katalog-
// Komposition für Seite und Export. Kernaussagen: (1) Quellen-Fehler
// werden BENANNT durchgereicht statt verschluckt, (2) die Producer laufen
// dabei mit leeren Daten weiter (#466-Banner-Semantik der Seite bleibt
// möglich; der Export prüft hasErrors und bricht ab), (3) die kanonische
// metric_id-Konvention `summary.<key>` entsteht genau hier.

import { describe, it, expect, vi } from 'vitest'

interface TableConfig {
  data?: unknown
  error?: { message: string } | null
}

function makeSupabase(tables: Record<string, TableConfig>) {
  const result = (table: string) =>
    Promise.resolve({ data: tables[table]?.data ?? [], error: tables[table]?.error ?? null })
  return {
    from: vi.fn((table: string) => ({
      select: vi.fn(() => ({
        in: vi.fn(() => result(table)),
        eq: vi.fn(() => {
          const p = result(table) as Promise<{ data: unknown; error: { message: string } | null }> & {
            eq?: unknown
          }
          // qaf_part kettet .eq().eq().maybeSingle()
          return Object.assign(p, {
            eq: vi.fn(() => ({
              maybeSingle: vi.fn(() =>
                Promise.resolve({ data: tables[table]?.data ?? null, error: tables[table]?.error ?? null }),
              ),
            })),
          })
        }),
      })),
    })),
  }
}

const COMPARISON = {
  id: '55555555-5555-4555-8555-555555555555',
  project_id: 'proj-1',
  part_number: 'P-1',
  baseline_file_id: 'file-alt',
  comparison_file_id: 'file-neu',
  // Engine-Config-Treue (#502-Review): null = vor KAR-896 persistiert →
  // resolvet vollständig auf den heutigen Default (bisheriges Verhalten).
  engine_version: null as unknown,
}

const METRIC_ROWS = [
  {
    file_id: 'file-alt',
    metric_key: 'materialCosts',
    value: 100,
    currency: 'EUR',
    source_cell: 'C21',
    sheet: 'Blatt',
    formula: null,
    value_state: null,
  },
  {
    file_id: 'file-neu',
    metric_key: 'materialCosts',
    value: 110,
    currency: 'EUR',
    source_cell: 'C21',
    sheet: 'Blatt',
    formula: null,
    value_state: null,
  },
]

const FILE_ROWS = [
  { id: 'file-alt', original_file_name: 'alt.xlsx', template_type: 'QAF_LEGACY_DE_SUMMARY', storage_key: null, capability_matrix: null },
  { id: 'file-neu', original_file_name: 'neu.xlsx', template_type: 'QAF_LEGACY_DE_SUMMARY', storage_key: null, capability_matrix: null },
]

async function load(tables: Record<string, TableConfig>) {
  const { loadComparisonComposition } = await import('../composition')
  return loadComparisonComposition(makeSupabase(tables) as never, COMPARISON)
}

describe('loadComparisonComposition', () => {
  it('composes run and catalogs from healthy sources — canonical metric_id convention included', async () => {
    const c = await load({
      qaf_file: { data: FILE_ROWS },
      qaf_summary_metric: { data: METRIC_ROWS },
      qaf_part: { data: { part_name: 'Teil', supplier: 'Lieferant', variant: null, quotation_date: null } },
    })
    expect(c.hasErrors).toBe(false)
    expect(c.errors).toEqual({
      files: null,
      metrics: null,
      stepMatches: null,
      mfgDiffs: null,
      mfgSteps: null,
      matDiffs: null,
      part: null,
    })
    expect(c.part?.supplier).toBe('Lieferant')
    expect(c.run.summaryDiffs.length).toBeGreaterThan(0)
    for (const metricId of c.metricIdByDifferenceId.values()) {
      expect(metricId).toMatch(/^summary\./)
    }
  })

  it('respektiert die persistierte Formel-Engine-Einstellung des Vergleichs (Review-Befund #502)', async () => {
    // Gleicher Wert, verschiedene Formel: mit laufender Engine ein
    // formel_geaendert-Befund, mit persistiertem enabled:false keiner —
    // der Lauf rechnet mit der Config des Vergleichs, nicht dem Default.
    const formelZeile = (fileId: string, formula: string) => ({
      file_id: fileId,
      metric_key: 'materialCosts',
      value: 100,
      currency: 'EUR',
      source_cell: 'C21',
      sheet: 'Blatt',
      formula,
      value_state: 'formula_and_cached',
    })
    const tables = {
      qaf_file: { data: FILE_ROWS },
      qaf_summary_metric: { data: [formelZeile('file-alt', '=A1*2'), formelZeile('file-neu', '=B9+7')] },
    }
    const { loadComparisonComposition } = await import('../composition')

    const mitDefault = await loadComparisonComposition(makeSupabase(tables) as never, COMPARISON)
    expect(mitDefault.run.summaryDiffs[0]?.formulaFinding).toBeDefined()

    const mitOverride = await loadComparisonComposition(makeSupabase(tables) as never, {
      ...COMPARISON,
      engine_version: { engineConfigOverrides: { formulaEngine: { enabled: false } } },
    })
    expect(mitOverride.run.summaryDiffs[0]?.formulaFinding).toBeUndefined()
    // Gegenprobe: die Zeile selbst ist in beiden Läufen da — nur der
    // Formel-Befund hängt an der Config.
    expect(mitOverride.run.summaryDiffs[0]?.metricKey).toBe('materialCosts')
  })

  it('names a failing source and still runs the producers on empty data (page banner semantics)', async () => {
    const c = await load({
      qaf_file: { data: FILE_ROWS },
      qaf_summary_metric: { data: METRIC_ROWS },
      qaf_manufacturing_diff: { data: null, error: { message: 'induced mfg failure' } },
      qaf_part: { data: null },
    })
    expect(c.hasErrors).toBe(true)
    expect(c.errors.mfgDiffs).toBe('induced mfg failure')
    expect(c.errors.metrics).toBeNull()
    // Producer liefen mit leeren Daten weiter — kein Crash, leerer Katalog.
    expect(c.mfgCatalog.records).toEqual([])
    expect(c.run.summaryDiffs.length).toBeGreaterThan(0)
  })

  it('is deterministic for identical input', async () => {
    const tables = {
      qaf_file: { data: FILE_ROWS },
      qaf_summary_metric: { data: METRIC_ROWS },
      qaf_part: { data: null },
    }
    const a = await load(tables)
    const b = await load(tables)
    expect(JSON.stringify({ run: a.run, errors: a.errors })).toBe(JSON.stringify({ run: b.run, errors: b.errors }))
  })
})
