// Unit tests for column-classifier.ts (KAR-930 / Multi-QAF-Programm P1.2).
//
// All facts/labels/formulas below are invented test data (no real cell
// content, no real project/vehicle codes) — this file only exercises the
// classification DECISION logic against hand-built ColumnClassificationFacts,
// never a real workbook grid (header-parser.test.ts covers the grid-reading
// side against the synthetic fixture builders).

import { describe, it, expect } from 'vitest'
import {
  classifyColumn,
  parseDifferenceFormula,
  parsePercentFormula,
  parseSumRangeFormula,
  isCommentLabel,
  type ColumnClassificationContext,
  type ColumnClassificationFacts,
} from '../column-classifier'

function facts(overrides: Partial<ColumnClassificationFacts>): ColumnClassificationFacts {
  return {
    column: 'S',
    columnIndex: 18,
    slotIndex: null,
    isIndexOutlier: false,
    hasIdentity: false,
    hasPositiveVolume: false,
    label: null,
    formulas: [],
    hasLiteralNumericContent: false,
    ...overrides,
  }
}

const EMPTY_CONTEXT: ColumnClassificationContext = {
  resolvedKinds: new Map(),
  variantIdByColumn: new Map(),
}

describe('formula-shape parsers', () => {
  it('parses a pure two-cell difference formula', () => {
    expect(parseDifferenceFormula('=AB15-S15')).toEqual({ colA: 'AB', colB: 'S' })
  })

  it('does not match a difference formula with a third term', () => {
    expect(parseDifferenceFormula('=AB15-S15-T15')).toBeNull()
  })

  it('parses a pure two-cell percent formula', () => {
    expect(parsePercentFormula('=AC15/S15')).toEqual({ colA: 'AC', colB: 'S' })
  })

  it('parses a SUM range formula', () => {
    expect(parseSumRangeFormula('=SUM(Q20:AB20)')).toEqual({ colFrom: 'Q', colTo: 'AB' })
  })

  it('rejects a SUM formula wrapped in extra arithmetic', () => {
    expect(parseSumRangeFormula('=SUM(Q20:AB20)+1')).toBeNull()
  })

  // KAR-930 review F4: ExcelJS's cell.formula is exactly what the analyst
  // typed — real spreadsheets are frequently hand-edited with whitespace
  // around the operator, which the regexes did not tolerate.
  it('parses a difference formula with whitespace around the operator', () => {
    expect(parseDifferenceFormula('=Q5 - S5')).toEqual({ colA: 'Q', colB: 'S' })
  })

  it('parses a percent formula with whitespace around the operator', () => {
    expect(parsePercentFormula('=AC15 / S15')).toEqual({ colA: 'AC', colB: 'S' })
  })

  it('parses a SUM formula with whitespace inside the parens/colon', () => {
    expect(parseSumRangeFormula('=SUM( Q20 : AB20 )')).toEqual({ colFrom: 'Q', colTo: 'AB' })
  })

  it('still rejects a difference formula with a third term even with whitespace', () => {
    expect(parseDifferenceFormula('=AB15 - S15 - T15')).toBeNull()
  })
})

describe('isCommentLabel', () => {
  it('matches DE and EN comment synonyms', () => {
    expect(isCommentLabel('Kommentar')).toBe(true)
    expect(isCommentLabel('Comment')).toBe(true)
    expect(isCommentLabel('Bemerkung zur Variante')).toBe(true)
  })

  it('does not match an unrelated label', () => {
    expect(isCommentLabel('Fahrzeug')).toBe(false)
    expect(isCommentLabel(null)).toBe(false)
  })
})

describe('classifyColumn — slot-index-run membership', () => {
  it('classifies a slot-index column with no identity as reserved_placeholder', () => {
    const result = classifyColumn(facts({ slotIndex: 13, hasIdentity: false, hasPositiveVolume: false }), EMPTY_CONTEXT)
    expect(result.kind).toBe('reserved_placeholder')
    expect(result.evidence.length).toBeGreaterThan(0)
  })

  it('classifies a slot-index column with identity and volume as active_product_variant', () => {
    const result = classifyColumn(facts({ slotIndex: 1, hasIdentity: true, hasPositiveVolume: true }), EMPTY_CONTEXT)
    expect(result.kind).toBe('active_product_variant')
  })

  it('classifies a slot-index column with identity but zero volume as inactive_product_variant', () => {
    const result = classifyColumn(facts({ slotIndex: 4, hasIdentity: true, hasPositiveVolume: false }), EMPTY_CONTEXT)
    expect(result.kind).toBe('inactive_product_variant')
  })
})

describe('classifyColumn — identity without a slot index (orphaned-but-real)', () => {
  it('classifies a non-indexed column with full identity + volume as active_product_variant', () => {
    const result = classifyColumn(
      facts({ slotIndex: null, isIndexOutlier: false, hasIdentity: true, hasPositiveVolume: true }),
      EMPTY_CONTEXT,
    )
    expect(result.kind).toBe('active_product_variant')
    expect(result.evidence.some((e) => e.signal === 'identity_no_index')).toBe(true)
  })

  it('flags an index-outlier column with identity distinctly from a never-indexed one', () => {
    const result = classifyColumn(
      facts({ slotIndex: null, isIndexOutlier: true, hasIdentity: true, hasPositiveVolume: true }),
      EMPTY_CONTEXT,
    )
    expect(result.evidence.some((e) => e.signal === 'identity_index_outlier')).toBe(true)
  })
})

describe('classifyColumn — no identity: formula-lineage evidence', () => {
  it('classifies a pure literal column with a benchmark-hinting label as benchmark_scenario', () => {
    const result = classifyColumn(
      facts({ label: 'qx9 LL vs. Programm', formulas: [], hasLiteralNumericContent: true }),
      EMPTY_CONTEXT,
    )
    expect(result.kind).toBe('benchmark_scenario')
    expect(result.confidence).toBeGreaterThan(0.8)
  })

  it('classifies a pure literal column with a plain label as benchmark_scenario at lower confidence', () => {
    const result = classifyColumn(facts({ label: 'Referenzwert', formulas: [], hasLiteralNumericContent: true }), EMPTY_CONTEXT)
    expect(result.kind).toBe('benchmark_scenario')
    expect(result.confidence).toBeLessThan(0.8)
  })

  it('classifies a pure literal column with NO label at all as unknown (never guessed)', () => {
    const result = classifyColumn(facts({ label: null, formulas: [], hasLiteralNumericContent: true }), EMPTY_CONTEXT)
    expect(result.kind).toBe('unknown')
  })

  it('classifies a difference formula between two active-variant columns as comparison_scenario', () => {
    const context: ColumnClassificationContext = {
      resolvedKinds: new Map([
        ['AF', 'active_product_variant'],
        ['S', 'active_product_variant'],
      ]),
      variantIdByColumn: new Map([
        ['AF', 'variant-af'],
        ['S', 'variant-s'],
      ]),
    }
    const result = classifyColumn(facts({ formulas: ['=AF15-S15'] }), context)
    expect(result.kind).toBe('comparison_scenario')
    expect(result.relatedVariantIds).toEqual(['variant-af', 'variant-s'])
  })

  it('classifies a whitespace-formatted difference formula as delta_column, not helper_calculation (KAR-930 review F4)', () => {
    const context: ColumnClassificationContext = {
      resolvedKinds: new Map([['AB', 'benchmark_scenario']]),
      variantIdByColumn: new Map(),
    }
    const result = classifyColumn(facts({ formulas: ['=AB15 - S15'] }), context)
    expect(result.kind).toBe('delta_column')
  })

  it('classifies a difference formula against a benchmark column as delta_column (not comparison_scenario)', () => {
    const context: ColumnClassificationContext = {
      resolvedKinds: new Map([
        ['AB', 'benchmark_scenario'],
        ['S', 'active_product_variant'],
      ]),
      variantIdByColumn: new Map([['S', 'variant-s']]),
    }
    const result = classifyColumn(facts({ formulas: ['=AB15-S15'] }), context)
    expect(result.kind).toBe('delta_column')
  })

  it('classifies a percent formula over an already-resolved delta column at higher confidence', () => {
    const context: ColumnClassificationContext = {
      resolvedKinds: new Map([['AC', 'delta_column']]),
      variantIdByColumn: new Map(),
    }
    const withoutContext = classifyColumn(facts({ formulas: ['=AC15/S15'] }), EMPTY_CONTEXT)
    const withContext = classifyColumn(facts({ formulas: ['=AC15/S15'] }), context)
    expect(withContext.kind).toBe('percentage_delta_column')
    expect(withoutContext.kind).toBe('percentage_delta_column')
    expect(withContext.confidence).toBeGreaterThan(withoutContext.confidence)
  })

  it('classifies a wide SUM-range formula as total', () => {
    const result = classifyColumn(facts({ formulas: ['=SUM(Q20:AT20)'] }), EMPTY_CONTEXT)
    expect(result.kind).toBe('total')
  })

  it('does not classify a narrow 2-column SUM as total', () => {
    const result = classifyColumn(facts({ formulas: ['=SUM(Q20:R20)'] }), EMPTY_CONTEXT)
    expect(result.kind).not.toBe('total')
  })

  it('classifies a comment-labeled column as comment_column even with no other signal', () => {
    const result = classifyColumn(facts({ label: 'Kommentar' }), EMPTY_CONTEXT)
    expect(result.kind).toBe('comment_column')
  })

  it('classifies an unrecognized formula shape as helper_calculation', () => {
    const result = classifyColumn(facts({ formulas: ['=ROUND(Q15,2)'] }), EMPTY_CONTEXT)
    expect(result.kind).toBe('helper_calculation')
  })

  it('classifies a column with no signal at all as unknown', () => {
    const result = classifyColumn(facts({}), EMPTY_CONTEXT)
    expect(result.kind).toBe('unknown')
    expect(result.confidence).toBeLessThan(0.3)
  })
})
