import { describe, expect, it } from 'vitest'
import { collectMaterialCostDrivers, driverChartLabel, topDriversByCurrency } from '../qaf-multi-qaf-material-chart'
import type { MaterialComponentRef, MaterialDiffResult, SharedComponentImpactSummary } from '@/lib/qaf-differences'

// FIXTURE-DATEN-REGEL: every id/label below is FREE INVENTION, not lifted
// from any real analyzed workbook.

function ref(identity: string): MaterialComponentRef {
  return { canonicalComponentIdentity: identity, sourceRow: 1, sourceCells: [], validationStatus: 'ok' }
}

function impact(aggregates: SharedComponentImpactSummary['aggregates']): SharedComponentImpactSummary {
  return { affectedVariantIds: [], impacts: [], aggregates }
}

function emptyMaterialDiff(overrides: Partial<MaterialDiffResult['sharedComponents']> = {}): MaterialDiffResult {
  return {
    sharedComponents: {
      unitCostValueChanges: [],
      unitCostCurrencyChanges: [],
      exchangeRateChanges: [],
      logisticsOrDutyChanges: [],
      materialOverheadChanges: [],
      formulaChanges: [],
      rowIdentityChanges: [],
      ...overrides,
    },
    variantAllocation: { findings: [], substitutionsSuspected: [] },
    uncertainMatches: [],
    warnings: [],
  }
}

describe('collectMaterialCostDrivers', () => {
  it('collects non-empty aggregates from unitCostValueChanges/logisticsOrDutyChanges/materialOverheadChanges', () => {
    const diff = emptyMaterialDiff({
      unitCostValueChanges: [
        {
          canonicalComponentIdentity: '10::Bracket A',
          alt: ref('10::Bracket A'),
          neu: ref('10::Bracket A'),
          altValue: 1,
          neuValue: 1.5,
          deltaAbsolute: 0.5,
          deltaPercent: 0.5,
          status: 'anstieg',
          impact: impact([{ currency: 'EUR', totalImpact: 500, variantCount: 2 }]),
          reviewRelevant: false,
        },
      ],
      materialOverheadChanges: [
        {
          field: 'material_overhead',
          canonicalComponentIdentity: '20::Bracket B',
          alt: ref('20::Bracket B'),
          neu: ref('20::Bracket B'),
          altAmount: { value: 2, currency: 'EUR' },
          neuAmount: { value: 1, currency: 'EUR' },
          valueChanged: true,
          currencyChanged: false,
          deltaAbsolute: -1,
          deltaPercent: -0.5,
          status: 'senkung',
          impact: impact([{ currency: 'EUR', totalImpact: -200, variantCount: 2 }]),
          reviewRelevant: false,
        },
      ],
    })

    const rows = collectMaterialCostDrivers(diff)
    expect(rows).toHaveLength(2)
    expect(rows.map((r) => r.totalImpact).sort((a, b) => a - b)).toEqual([-200, 500])
  })

  it('contributes nothing from currency-swap/exchange-rate/formula findings — their aggregates are always empty by construction', () => {
    const diff = emptyMaterialDiff({
      unitCostCurrencyChanges: [
        {
          canonicalComponentIdentity: '30::Bracket C',
          alt: ref('30::Bracket C'),
          neu: ref('30::Bracket C'),
          altCurrency: 'EUR',
          neuCurrency: 'USD',
          impact: impact([]),
          reviewRelevant: true,
        },
      ],
      exchangeRateChanges: [
        {
          canonicalComponentIdentity: '40::Bracket D',
          alt: ref('40::Bracket D'),
          neu: ref('40::Bracket D'),
          altValue: 1.1,
          neuValue: 1.2,
          deltaAbsolute: 0.1,
          deltaPercent: 0.09,
          status: 'anstieg',
          impact: impact([]),
          reviewRelevant: false,
        },
      ],
      formulaChanges: [
        {
          canonicalComponentIdentity: '50::Bracket E',
          alt: ref('50::Bracket E'),
          neu: ref('50::Bracket E'),
          comparisonKind: 'formel_geaendert_wert_gleich',
          explanation: 'Test-Fixture.',
          impact: impact([]),
          reviewRelevant: false,
        },
      ],
    })

    expect(collectMaterialCostDrivers(diff)).toHaveLength(0)
  })

  it('returns an empty list for a diff with no shared-component findings at all', () => {
    expect(collectMaterialCostDrivers(emptyMaterialDiff())).toHaveLength(0)
  })
})

describe('topDriversByCurrency', () => {
  it('groups strictly per currency — never mixes EUR and USD rows in one group', () => {
    const rows = [
      { key: 'a', label: 'A', currency: 'EUR', totalImpact: 100, variantCount: 1 },
      { key: 'b', label: 'B', currency: 'USD', totalImpact: -50, variantCount: 1 },
    ]
    const grouped = topDriversByCurrency(rows)
    expect([...grouped.keys()].sort()).toEqual(['EUR', 'USD'])
    expect(grouped.get('EUR')).toHaveLength(1)
    expect(grouped.get('USD')).toHaveLength(1)
  })

  it('sorts by |totalImpact| descending and caps at n per currency', () => {
    const rows = [
      { key: 'a', label: 'A', currency: 'EUR', totalImpact: 10, variantCount: 1 },
      { key: 'b', label: 'B', currency: 'EUR', totalImpact: -900, variantCount: 1 },
      { key: 'c', label: 'C', currency: 'EUR', totalImpact: 300, variantCount: 1 },
    ]
    const grouped = topDriversByCurrency(rows, 2)
    const eur = grouped.get('EUR')!
    expect(eur).toHaveLength(2)
    expect(eur.map((r) => r.key)).toEqual(['b', 'c'])
  })
})

// Review finding 4: Top-8-per-currency cut without an "X von Y" disclosure.
describe('driverChartLabel', () => {
  it('discloses the cut as "Top 8 von 10" when a currency has more drivers than shown', () => {
    expect(driverChartLabel('EUR', 8, 10)).toBe('Top 8 von 10 Kostentreiber (EUR)')
  })

  it('omits the "X von Y" suffix when nothing was capped (shown === total)', () => {
    expect(driverChartLabel('EUR', 5, 5)).toBe('Top-Kostentreiber (EUR)')
  })
})
