import { describe, it, expect } from 'vitest'
import {
  autoMatchQafRows,
  autoMatchQafFiles,
  compositeKey,
  fuzzySuggest,
  scoreCandidate,
  type QafRowForMapping,
  type QafFileForMapping,
  type ProcessStepForMapping,
} from '@/lib/qaf/process-mapping'

const steps: ProcessStepForMapping[] = [
  { id: 's1', step_number: 1, station_name: 'Fräsen', area_name: 'Mechanik' },
  { id: 's2', step_number: 2, station_name: 'Bohren', area_name: 'Mechanik' },
  { id: 's3', step_number: 3, station_name: 'Spritzguss A', area_name: 'Kunststoff' },
  { id: 's4', step_number: 4, station_name: 'Montage', area_name: 'Endmontage' },
]

describe('autoMatchQafRows (exact-match by prozessbezeichnung == station_name)', () => {
  it('matches identical names case-insensitive', () => {
    const rows: QafRowForMapping[] = [
      { key: 'r1', prozessbezeichnung: 'Fräsen' },
      { key: 'r2', prozessbezeichnung: 'BOHREN' },
    ]
    const result = autoMatchQafRows(rows, steps)
    expect(result.matched.get('r1')).toEqual({ step_id: 's1', confidence: 1.0 })
    expect(result.matched.get('r2')).toEqual({ step_id: 's2', confidence: 1.0 })
    expect(result.unmatched).toEqual([])
  })

  it('returns unmatched rows for non-existing names', () => {
    const rows: QafRowForMapping[] = [
      { key: 'r1', prozessbezeichnung: 'Fräsen' },
      { key: 'r2', prozessbezeichnung: 'XYZ unbekannt' },
    ]
    const result = autoMatchQafRows(rows, steps)
    expect(result.matched.size).toBe(1)
    expect(result.unmatched.map((r) => r.key)).toEqual(['r2'])
  })

  it('handles empty input', () => {
    const result = autoMatchQafRows([], steps)
    expect(result.matched.size).toBe(0)
    expect(result.unmatched).toEqual([])
  })

  it('skips rows with empty prozessbezeichnung', () => {
    const rows: QafRowForMapping[] = [
      { key: 'r1', prozessbezeichnung: '' },
      { key: 'r2', prozessbezeichnung: '   ' },
    ]
    const result = autoMatchQafRows(rows, steps)
    expect(result.unmatched.map((r) => r.key)).toEqual(['r1', 'r2'])
  })
})

describe('scoreCandidate (token-overlap-based fuzzy score 0-1)', () => {
  it('returns 1 for identical strings', () => {
    expect(scoreCandidate('Fräsen', 'Fräsen')).toBe(1)
  })

  it('returns higher score for shared tokens', () => {
    const high = scoreCandidate('Spritzguss A', 'Spritzguss B')
    const low = scoreCandidate('Spritzguss A', 'Montage')
    expect(high).toBeGreaterThan(low)
  })

  it('returns 0 for completely different strings', () => {
    expect(scoreCandidate('abc', 'xyz')).toBe(0)
  })

  it('is case-insensitive', () => {
    expect(scoreCandidate('FRÄSEN', 'fräsen')).toBe(1)
  })

  it('handles empty strings', () => {
    expect(scoreCandidate('', 'foo')).toBe(0)
    expect(scoreCandidate('foo', '')).toBe(0)
    expect(scoreCandidate('', '')).toBe(0)
  })
})

describe('fuzzySuggest (returns top-N candidates sorted by score desc)', () => {
  it('returns empty when no candidates score above threshold', () => {
    const result = fuzzySuggest('XYZ ganz fremd', steps, { topN: 3, minScore: 0.3 })
    expect(result).toEqual([])
  })

  it('returns top-N candidates sorted descending', () => {
    const result = fuzzySuggest('Spritzguss', steps, { topN: 2, minScore: 0.1 })
    expect(result.length).toBeLessThanOrEqual(2)
    if (result.length >= 2) {
      expect(result[0].score).toBeGreaterThanOrEqual(result[1].score)
    }
    expect(result[0]?.step.id).toBe('s3')
  })

  it('respects minScore threshold', () => {
    const lowThreshold = fuzzySuggest('Spritzguss', steps, { topN: 5, minScore: 0.0 })
    const highThreshold = fuzzySuggest('Spritzguss', steps, { topN: 5, minScore: 0.9 })
    expect(lowThreshold.length).toBeGreaterThanOrEqual(highThreshold.length)
  })
})

describe('autoMatchQafFiles (KAR-340 multi-file variant)', () => {
  const filesIdentical: QafFileForMapping[] = [
    {
      id: 'file-A',
      label: 'A.xlsx',
      rows: [
        { key: 'r1', prozessbezeichnung: 'Fräsen' },
        { key: 'r2', prozessbezeichnung: 'Montage' },
      ],
    },
    {
      id: 'file-B',
      label: 'B.xlsx',
      rows: [
        { key: 'r1', prozessbezeichnung: 'Fräsen' },
        { key: 'r2', prozessbezeichnung: 'XYZ-fremd' },
      ],
    },
  ]

  it('matches the same row_key in two files to distinct composite keys', () => {
    const res = autoMatchQafFiles(filesIdentical, steps)
    expect(res.matched.get('file-A::r1')).toEqual({ step_id: 's1', confidence: 1.0 })
    expect(res.matched.get('file-B::r1')).toEqual({ step_id: 's1', confidence: 1.0 })
  })

  it('unmatched rows carry their qaf_upload_id', () => {
    const res = autoMatchQafFiles(filesIdentical, steps)
    const xyz = res.unmatched.find((r) => r.prozessbezeichnung === 'XYZ-fremd')
    expect(xyz?.qaf_upload_id).toBe('file-B')
  })

  it('handles empty file list', () => {
    const res = autoMatchQafFiles([], steps)
    expect(res.matched.size).toBe(0)
    expect(res.unmatched).toEqual([])
  })

  it('compositeKey is stable across calls', () => {
    expect(compositeKey('f1', 'r1')).toBe('f1::r1')
    expect(compositeKey(null, 'r1')).toBe('__nofile__::r1')
    expect(compositeKey(undefined, 'r1')).toBe('__nofile__::r1')
  })
})
