// preview.ts tests (QVS-P2, KAR-971).

import { describe, it, expect, vi, beforeEach } from 'vitest'
import type { QAFRow } from '@/lib/qaf-parser'
import type { QafSourceRows } from '../qaf-source'
import type { QvsExistingImport } from '../duplicates'

const { loadQafSourceRowsMock } = vi.hoisted(() => ({ loadQafSourceRowsMock: vi.fn() }))
const { findExistingImportsMock } = vi.hoisted(() => ({ findExistingImportsMock: vi.fn() }))
// QVS-P4: buildQafValueStreamPreview now also calls loadMultiQafVariantContext
// — mocked to `null` by default (the common "not a Multi-QAF-related file"
// case) so every pre-existing test in this file, which never sets up a
// qaf_comparison/qaf_file mock, keeps working unchanged.
const { loadMultiQafVariantContextMock } = vi.hoisted(() => ({ loadMultiQafVariantContextMock: vi.fn() }))

vi.mock('../qaf-source', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../qaf-source')>()
  return { ...actual, loadQafSourceRows: loadQafSourceRowsMock }
})
vi.mock('../duplicates', () => ({ findExistingImports: findExistingImportsMock }))
vi.mock('../multi-qaf-context', () => ({ loadMultiQafVariantContext: loadMultiQafVariantContextMock }))

import { buildPreviewFromRows, buildQafValueStreamPreview } from '../preview'

function makeRow(overrides: Partial<QAFRow> = {}): QAFRow {
  return {
    positionsnummer: '',
    teilebenennung: '',
    prozessbezeichnung: '',
    bezeichnungAnlage: '',
    standort: '',
    beschaffungswaehrung: '',
    zykluszeit: null,
    teileProZyklus: null,
    anzahlMA: null,
    lohnkosten: null,
    lohnzuschlagssaetze: null,
    mss: null,
    ruestkosten: null,
    fek: null,
    rfgk: null,
    fk: null,
    angebotswaehrung: '',
    wechselkurs: null,
    anzahlProAngebotsteil: null,
    fkAW: null,
    ausschuss: null,
    ausschusskosten: null,
    ...overrides,
  }
}

const SOURCE: QafSourceRows = {
  qafFile: {
    id: 'file-1',
    projectId: 'project-1',
    fileHash: 'hash-1',
    originalFileName: 'x.xlsx',
    parserVersion: 'p1',
    fileLevelContext: { plannedCapacityPartsPerYear: null, lotSizeParts: null },
  },
  rows: [makeRow({ prozessbezeichnung: 'Schweißen' }), makeRow({ prozessbezeichnung: '' })],
  stepIds: ['step-0', 'step-1'],
}

const DUPLICATE_ITEMS: QvsExistingImport[] = [
  { importId: 'import-0', valueStreamId: 'vsm-0', createdAt: '2026-07-01T00:00:00Z', fileHash: 'hash-1', variantSelector: null },
]
const DUPLICATES = { items: DUPLICATE_ITEMS, degraded: false }
const DUPLICATES_DEGRADED = { items: [], degraded: true, degradedReason: 'relation "value_stream_imports" does not exist' }
const NO_DUPLICATES = { items: [], degraded: false }

beforeEach(() => {
  loadQafSourceRowsMock.mockReset()
  findExistingImportsMock.mockReset()
  loadMultiQafVariantContextMock.mockReset()
  loadMultiQafVariantContextMock.mockResolvedValue(null)
})

describe('buildPreviewFromRows', () => {
  it('assembles capability, mapped nodes, previewToken, title and duplicates', () => {
    const preview = buildPreviewFromRows(SOURCE, DUPLICATES, { titleParts: { supplier: 'Acme', project: 'P1' } })

    expect(preview.qafFileId).toBe('file-1')
    expect(preview.projectId).toBe('project-1')
    expect(preview.parserVersion).toBe('p1')
    expect(preview.sourceFileName).toBe('x.xlsx')
    expect(preview.capability.eligible).toBe(true)
    expect(preview.capability.stepCount).toBe(1)
    expect(preview.nodes).toHaveLength(1)
    expect(preview.excludedRows).toEqual([{ rowIndex: 1, reason: 'missing_process_name' }])
    expect(preview.suggestedTitle).toBe('Acme · P1 · QAF-Wertstrom')
    expect(preview.duplicates).toBe(DUPLICATE_ITEMS)
    expect(preview.duplicateCheckDegraded).toBe(false)
    expect(preview.previewToken).toHaveLength(64) // sha256 hex
  })

  it('falls back to just the suffix title when no titleParts are given', () => {
    const preview = buildPreviewFromRows(SOURCE, NO_DUPLICATES)
    expect(preview.suggestedTitle).toBe('QAF-Wertstrom')
  })

  // Review-Fix 3: a degraded duplicate-check result must surface as
  // `duplicateCheckDegraded: true` on the preview, never silently collapse
  // into the same shape a genuine "0 duplicates" result would have.
  it('surfaces a degraded duplicate-check as duplicateCheckDegraded: true, not as a clean 0-duplicates result', () => {
    const preview = buildPreviewFromRows(SOURCE, DUPLICATES_DEGRADED)
    expect(preview.duplicates).toEqual([])
    expect(preview.duplicateCheckDegraded).toBe(true)
  })
})

describe('buildQafValueStreamPreview', () => {
  it('returns null when loadQafSourceRows returns null — never calls findExistingImports', async () => {
    loadQafSourceRowsMock.mockResolvedValueOnce(null)
    const result = await buildQafValueStreamPreview({} as never, 'file-1')
    expect(result).toBeNull()
    expect(findExistingImportsMock).not.toHaveBeenCalled()
  })

  it('looks up duplicates by the SERVER-resolved project_id/file_hash, not any client input', async () => {
    loadQafSourceRowsMock.mockResolvedValueOnce(SOURCE)
    findExistingImportsMock.mockResolvedValueOnce(DUPLICATES)

    const result = await buildQafValueStreamPreview({} as never, 'file-1')

    expect(findExistingImportsMock).toHaveBeenCalledWith({}, { projectId: 'project-1', fileHash: 'hash-1' })
    expect(result?.duplicates).toBe(DUPLICATE_ITEMS)
    expect(result?.duplicateCheckDegraded).toBe(false)
  })

  // QVS-P4
  it('calls loadMultiQafVariantContext with the qafFileId and surfaces its result on the preview', async () => {
    loadQafSourceRowsMock.mockResolvedValueOnce(SOURCE)
    findExistingImportsMock.mockResolvedValueOnce(NO_DUPLICATES)
    const variantContext = { variants: [{ variantKey: 'region=eu', label: 'EU', volume: 1000 }], degraded: false, sharedProfileConfirmed: true }
    loadMultiQafVariantContextMock.mockResolvedValueOnce(variantContext)

    const result = await buildQafValueStreamPreview({} as never, 'file-1')

    expect(loadMultiQafVariantContextMock).toHaveBeenCalledWith({}, 'file-1')
    expect(result?.variantContext).toEqual(variantContext)
  })

  it('surfaces null variantContext (the common non-Multi-QAF case) unchanged', async () => {
    loadQafSourceRowsMock.mockResolvedValueOnce(SOURCE)
    findExistingImportsMock.mockResolvedValueOnce(NO_DUPLICATES)
    loadMultiQafVariantContextMock.mockResolvedValueOnce(null)

    const result = await buildQafValueStreamPreview({} as never, 'file-1')

    expect(result?.variantContext).toBeNull()
  })

  it('passes source.qafFile.fileLevelContext through onto the preview', async () => {
    const sourceWithCapacity: QafSourceRows = {
      ...SOURCE,
      qafFile: { ...SOURCE.qafFile, fileLevelContext: { plannedCapacityPartsPerYear: 221500, lotSizeParts: 250 } },
    }
    loadQafSourceRowsMock.mockResolvedValueOnce(sourceWithCapacity)
    findExistingImportsMock.mockResolvedValueOnce(NO_DUPLICATES)
    loadMultiQafVariantContextMock.mockResolvedValueOnce(null)

    const result = await buildQafValueStreamPreview({} as never, 'file-1')

    expect(result?.fileLevelContext).toEqual({ plannedCapacityPartsPerYear: 221500, lotSizeParts: 250 })
  })
})

describe('buildPreviewFromRows — variantContext/fileLevelContext (QVS-P4)', () => {
  it('defaults variantContext to null when options.variantContext is not supplied (pre-P4 callers unaffected)', () => {
    const preview = buildPreviewFromRows(SOURCE, DUPLICATES)
    expect(preview.variantContext).toBeNull()
  })

  it('passes through an explicitly supplied variantContext', () => {
    const variantContext = { variants: [{ variantKey: 'v1', label: 'Variant 1', volume: null }], degraded: false, sharedProfileConfirmed: false }
    const preview = buildPreviewFromRows(SOURCE, DUPLICATES, { variantContext })
    expect(preview.variantContext).toEqual(variantContext)
  })

  it('always carries fileLevelContext from the source (both null when the file predates QVS-P4)', () => {
    const preview = buildPreviewFromRows(SOURCE, DUPLICATES)
    expect(preview.fileLevelContext).toEqual({ plannedCapacityPartsPerYear: null, lotSizeParts: null })
  })
})
