import { describe, it, expect } from 'vitest'
import type { VsmNode } from '@/lib/vsm-types'
import type { SummaryDiffRowData } from '@/lib/qaf-differences'
import {
  computeFactoryAnalysisKpi,
  computeQafKpi,
  computeValueStreamKpi,
  computeMeasuresKpi,
} from '../project-kpis'

// ─── computeFactoryAnalysisKpi ──────────────────────────────────────────────

describe('computeFactoryAnalysisKpi', () => {
  it('returns null when the project has no assessment at all', () => {
    expect(computeFactoryAnalysisKpi(false, [])).toBeNull()
    expect(computeFactoryAnalysisKpi(false, [{ is_relevant: true, selected_rating: 3 }])).toBeNull()
  })

  it('returns zeroed stats (not null) when an assessment exists but nothing is answered yet', () => {
    expect(computeFactoryAnalysisKpi(true, [])).toEqual({
      answeredCount: 0,
      scoreAvg: null,
      fulfilledCount: 0,
      partialCount: 0,
      notFulfilledCount: 0,
    })
  })

  it('excludes not-relevant responses and unanswered (null rating) responses from the score', () => {
    const result = computeFactoryAnalysisKpi(true, [
      { is_relevant: false, selected_rating: 1 }, // excluded: not relevant
      { is_relevant: true, selected_rating: null }, // excluded: unanswered
      { is_relevant: true, selected_rating: 3 },
    ])
    expect(result).toEqual({
      answeredCount: 1,
      scoreAvg: 3,
      fulfilledCount: 1,
      partialCount: 0,
      notFulfilledCount: 0,
    })
  })

  it('buckets rating 1 as not-fulfilled, 2 as partial, 3 and 4 as fulfilled, and averages all answered ratings', () => {
    const result = computeFactoryAnalysisKpi(true, [
      { is_relevant: true, selected_rating: 1 },
      { is_relevant: true, selected_rating: 2 },
      { is_relevant: true, selected_rating: 3 },
      { is_relevant: true, selected_rating: 4 },
    ])
    expect(result).toEqual({
      answeredCount: 4,
      scoreAvg: 2.5,
      fulfilledCount: 2,
      partialCount: 1,
      notFulfilledCount: 1,
    })
  })
})

// ─── computeQafKpi ───────────────────────────────────────────────────────────

describe('computeQafKpi', () => {
  it('returns null when the project has no qaf_comparison at all', () => {
    expect(computeQafKpi(null, [])).toBeNull()
  })

  it('returns kpis: null when the newest comparison has no persisted summary-diff rows (e.g. Multi-QAF)', () => {
    const result = computeQafKpi({ title: 'Variante A', part_number: '123' }, [])
    expect(result).toEqual({ comparisonTitle: 'Variante A', partNumber: '123', kpis: null })
  })

  it('passes persisted diff rows straight through to buildSummaryKpis without recomputation', () => {
    const rows: SummaryDiffRowData[] = [
      {
        metric_key: 'totalProductionCosts',
        alt_value: 100,
        neu_value: 90,
        delta_absolute: -10,
        delta_percent: -10,
        currency: 'EUR',
        status: 'senkung',
        source_alt: 'A1',
        source_neu: 'A1',
      },
    ]
    const result = computeQafKpi({ title: 'Neuester Vergleich', part_number: 'P-1' }, rows)
    expect(result?.kpis?.totalProductionCosts).toEqual({
      alt: 100,
      neu: 90,
      deltaAbsolute: -10,
      deltaPercent: -10,
    })
    expect(result?.kpis?.currency).toBe('EUR')
  })
})

// ─── computeValueStreamKpi ───────────────────────────────────────────────────

function processNode(overrides: Partial<VsmNode> & { id: string; name: string }): VsmNode {
  return { type: 'process', x: 0, y: 0, ...overrides }
}

describe('computeValueStreamKpi', () => {
  it('returns null when the project has no value-stream map at all', () => {
    expect(computeValueStreamKpi(null)).toBeNull()
  })

  it('reports null lead time / VA ratio / bottleneck (never a misleading 0) for a map with no timed nodes', () => {
    expect(computeValueStreamKpi({ title: 'Leerer Wertstrom', nodes: [] })).toEqual({
      title: 'Leerer Wertstrom',
      leadTimeSec: null,
      vaRatioPct: null,
      bottleneckName: null,
    })
  })

  it('derives lead time, VA ratio and the bottleneck station name from the nodes via vsm-metrics', () => {
    const nodes: VsmNode[] = [
      processNode({ id: 'a', name: 'Station A', cycleTimeSec: 30, vaClass: 'va' }),
      processNode({ id: 'b', name: 'Station B (Engpass)', cycleTimeSec: 90, vaClass: 'nva' }),
    ]
    const result = computeValueStreamKpi({ title: 'Linie 1', nodes })
    expect(result?.title).toBe('Linie 1')
    expect(result?.leadTimeSec).toBe(120)
    expect(result?.vaRatioPct).toBe(25)
    expect(result?.bottleneckName).toBe('Station B (Engpass)')
  })
})

// ─── computeMeasuresKpi ──────────────────────────────────────────────────────

describe('computeMeasuresKpi', () => {
  it('returns null when both tables are empty', () => {
    expect(computeMeasuresKpi([], [])).toBeNull()
  })

  it('combines both tables into open/inProgress/done — total equals the sum of the visible buckets, rejected excluded (PR #347 review fix)', () => {
    const result = computeMeasuresKpi(
      [{ status: 'offen' }, { status: 'in_arbeit' }, { status: 'erledigt' }],
      [{ status: 'open' }, { status: 'in_progress' }, { status: 'done' }, { status: 'rejected' }],
    )
    expect(result).toEqual({
      total: 6, // NOT 7 — 'rejected' is no measure stock
      open: 2, // 1 lsc 'offen' + 1 workshop 'open'
      inProgress: 2, // 1 lsc 'in_arbeit' + 1 workshop 'in_progress'
      done: 2, // 1 lsc 'erledigt' + 1 workshop 'done'
    })
  })

  it('counts only lsc_measures when workshop_actions is empty, and vice versa', () => {
    expect(computeMeasuresKpi([{ status: 'offen' }], [])).toEqual({ total: 1, open: 1, inProgress: 0, done: 0 })
    expect(computeMeasuresKpi([], [{ status: 'done' }])).toEqual({ total: 1, open: 0, inProgress: 0, done: 1 })
  })

  it('returns null when only rejected actions exist — nothing to display is not "0 gesamt"', () => {
    expect(computeMeasuresKpi([], [{ status: 'rejected' }])).toBeNull()
  })
})
