// @vitest-environment jsdom
//
// Abnahme der Übersichts-Sektion (Spezifikation Kap. 26, Invarianten U-01…U-04).
//
// Die Spec nennt in U-06 „Screenshot-/Snapshot-Tests je Sektion". Hier stehen
// stattdessen gezielte Zusicherungen, aus zwei Gründen: Das Repo führt bisher
// kein Snapshot-Werkzeug, und ein Snapshot hält fest, *was* gerendert wird,
// ohne zu prüfen, *ob es stimmt* — er wäre nach jeder Änderung mit einem
// `-u` erneuerbar, ohne dass jemand hinsieht. Die Invarianten unten fallen
// dagegen genau dann um, wenn die Aussage kippt.
//
// FIXTURE-DATEN-REGEL: die Daten kommen aus reference-case.ts und sind dort
// sämtlich frei erfunden.

import { describe, it, expect, afterEach } from 'vitest'
import { cleanup, render, screen, within } from '@testing-library/react'
import { QafV2ReferenceSection } from '../qaf-v2-reference-section'
import {
  buildReferenceView,
  REFERENCE_CASE_BROKEN,
  REFERENCE_CASE_FLAT,
  REFERENCE_CASE_OK,
} from '@/lib/qaf-differences/internal/reference-case'

afterEach(cleanup)

describe('Übersichts-Sektion — der Fall, der schliesst', () => {
  const view = buildReferenceView(REFERENCE_CASE_OK)

  it('nennt Teil, Vergleichsart und Vergleichbarkeit im Kopf', () => {
    render(<QafV2ReferenceSection view={view} />)

    expect(screen.getByText(/Baugruppe Muster A/)).toBeTruthy()
    expect(screen.getByText('Zeitlicher Änderungsvergleich')).toBeTruthy()
    expect(screen.getByText('Vergleichbar')).toBeTruthy()
  })

  it('zeigt beide Quelldateien mit ihrem Stand', () => {
    render(<QafV2ReferenceSection view={view} />)

    expect(screen.getByText(/muster-a-vergabe\.xlsx/)).toBeTruthy()
    expect(screen.getByText(/muster-a-aktuell\.xlsx/)).toBeTruthy()
  })

  it('zeichnet die Brücke und trägt kein Fehlerbanner', () => {
    const { container } = render(<QafV2ReferenceSection view={view} />)

    expect(container.querySelector('figure')).not.toBeNull()
    expect(screen.queryByRole('alert')).toBeNull()
  })

  it('U-01: jede Kachel trägt ihre Quelle mit sich', () => {
    // Ohne nachschlagbare Herkunft ist eine Zahl in einer Verhandlung nichts
    // wert — das ist der Grund, aus dem die Vorgängerfassung gescheitert ist.
    const { container } = render(<QafV2ReferenceSection view={view} />)
    const kacheln = container.querySelectorAll('.grid > button')

    expect(kacheln.length).toBe(view.tiles.length)
    for (const [i, kachel] of [...kacheln].entries()) {
      const tile = view.tiles[i]
      // Kacheln ohne belegte Differenz können keine Quelle tragen; alle
      // anderen müssen.
      if (tile.sourceRef.cells.length > 0) {
        expect(kachel.getAttribute('title')).toMatch(/^Quelle: /)
      }
    }
  })

  it('U-03: der angezeigte Wert ist der Wert des Falls, nicht neu gerundet', () => {
    const { container } = render(<QafV2ReferenceSection view={view} />)
    // Innerhalb der Kachel prüfen, nicht auf der ganzen Seite: der Endstand
    // steht auch am Brückenbalken, und dann sagt ein Treffer nichts darüber,
    // ob die Kachel ihn zeigt.
    const kachel = container.querySelectorAll('.grid > button')[0] as HTMLElement

    // 3,53 EUR/Stück mit vier Nachkommastellen aus der Einheit.
    expect(within(kachel).getByText('3,5300 EUR/Stück')).toBeTruthy()
    // Das Delta trägt sein Vorzeichen sichtbar, nicht nur die Farbe.
    expect(within(kachel).getByText('+1,0700 EUR/Stück')).toBeTruthy()
  })

  it('kürzt den Millionenbetrag, statt Ziffern zählen zu lassen', () => {
    const { container } = render(<QafV2ReferenceSection view={view} />)
    const lifetime = container.querySelectorAll('.grid > button')[1] as HTMLElement

    expect(within(lifetime).getByText('2,97 Mio. EUR')).toBeTruthy()
    // Das Delta liegt unter einer Million und bleibt deshalb ausgeschrieben —
    // die Grenze wirkt je Zahl, nicht je Kachel.
    expect(within(lifetime).getByText('+898.800,00 EUR')).toBeTruthy()
  })

  it('stellt Anfangs- und Endstand in ihrem tatsächlichen Verhältnis dar', () => {
    // Der Kern der Beschwerde, hier an der zusammengesetzten Ansicht. Auf der
    // früheren beschnittenen Skala standen zwei Stände, die 5 % auseinander
    // lagen, als 4,5 % hoher Strich neben einem 92,5 % hohen Klotz — das las
    // sich, als hätte der Vergabestand fast nichts gekostet.
    const { container } = render(<QafV2ReferenceSection view={view} />)
    // Über data-role statt über die DOM-Reihenfolge: die Konnektorlinie und
    // die Wertbeschriftung sind Geschwister des Balkens.
    const balken = container.querySelectorAll('figure [data-role="bar"]')

    expect(balken.length).toBe(view.waterfall.bars.length)
    const anfang = parseFloat((balken[0] as HTMLElement).style.height)
    const ende = parseFloat((balken[balken.length - 1] as HTMLElement).style.height)

    expect(anfang).toBeGreaterThan(50)
    expect(anfang / ende).toBeCloseTo(2.46 / 3.53, 2)
  })

  it('positioniert Balken und Konnektor gegen dieselbe Bezugsgrösse', () => {
    // Die Ursache, die drei Nachbesserungen überlebt hat: Der Balken wurde über
    // `margin-bottom` in Prozent gesetzt — und Prozentwerte bei `margin`
    // beziehen sich in CSS auf die BREITE des Elternelements, nicht auf die
    // Höhe. Der Konnektor nutzte `bottom`, das korrekt gegen die Höhe rechnet.
    // Beide massen also gegen verschiedene Grössen, und nichts passte zusammen.
    const { container } = render(<QafV2ReferenceSection view={view} />)

    for (const balken of container.querySelectorAll('figure [data-role="bar"]')) {
      const style = (balken as HTMLElement).style
      expect(style.bottom).not.toBe('')
      expect(style.marginBottom).toBe('')
    }
  })

  it('beschriftet die Achse, damit die Balken ablesbar sind', () => {
    const { container } = render(<QafV2ReferenceSection view={view} />)
    const figure = container.querySelector('figure') as HTMLElement

    // Die Achse beginnt bei null — sonst wäre jeder Höhenvergleich wertlos.
    expect(within(figure).getByText('0,0000 EUR/Stück')).toBeTruthy()
  })

  it('verbindet die Balken mit Konnektoren, die auch bis zum nächsten reichen', () => {
    // Kap. 22 verlangt sie. Ohne sie steht da eine Reihe schwebender Klötze.
    //
    // Die erste Fassung war schlimmer als keine: Sie lief nur über die
    // Flex-Lücke (6 px), während zwischen zwei mittig gesetzten Balken rund
    // 126 px liegen — die Linie schwebte frei in der Lücke und berührte keinen
    // der beiden Balken.
    const { container } = render(<QafV2ReferenceSection view={view} />)
    const konnektoren = container.querySelectorAll('figure [data-role="connector"]')

    // Einer je Balken ausser dem letzten.
    expect(konnektoren.length).toBe(view.waterfall.bars.length - 1)
    for (const k of konnektoren) {
      // Von Balkenmitte zu Balkenmitte, also über die volle Spaltenbreite.
      expect(k.className).toContain('left-1/2')
      expect(k.className).toContain('w-[calc(100%+0.375rem)]')
    }
  })

  it('macht den Zellnachweis über beide Stände auf', () => {
    const { container } = render(<QafV2ReferenceSection view={view} />)
    const tabelle = container.querySelector('table')

    expect(tabelle).not.toBeNull()
    expect(within(tabelle as HTMLElement).getByText('Vergabestand')).toBeTruthy()
    expect(within(tabelle as HTMLElement).getByText('Aktueller Stand')).toBeTruthy()
    // Zweimal: beide Stände tragen denselben Zellbezug. Genau deshalb hängt
    // der Befund an der Datei und nicht nur an Blatt und Zelle.
    expect(within(tabelle as HTMLElement).getAllByText('FERTIGUNG!M22')).toHaveLength(2)
  })
})

describe('Übersichts-Sektion — kleine Schritte auf hohem Niveau', () => {
  const view = buildReferenceView(REFERENCE_CASE_FLAT)

  it('zeigt die Stände richtig und macht die Schritte zu Linien', () => {
    // Die Grenze jeder Brücke, die bei null beginnt: 87,42 auf 92,15 sind
    // 5 %, und ein Schritt von 0,16 EUR ist auf dieser Skala nicht mehr als
    // Fläche darstellbar. Statt ihn wegzulassen oder künstlich zu vergrössern,
    // wird er zur Linie mit seinem Wert.
    const { container } = render(<QafV2ReferenceSection view={view} />)
    const balken = container.querySelectorAll('figure [data-role="bar"]')

    const anfang = parseFloat((balken[0] as HTMLElement).style.height)
    const ende = parseFloat((balken[balken.length - 1] as HTMLElement).style.height)
    expect(anfang / ende).toBeCloseTo(87.42 / 92.15, 2)

    const haarlinien = [...balken].filter((b) => b.getAttribute('data-hairline') === 'true')
    expect(haarlinien.length).toBeGreaterThan(0)
  })

  it('nennt den Grund, warum Schritte als Linie erscheinen', () => {
    render(<QafV2ReferenceSection view={view} />)

    expect(screen.getByText(/klein gegenüber den Ständen/)).toBeTruthy()
  })
})

describe('Übersichts-Sektion — der Fall, der nicht schliesst', () => {
  const view = buildReferenceView(REFERENCE_CASE_BROKEN)

  it('zeichnet keine Brücke, sondern die Diagnose', () => {
    const { container } = render(<QafV2ReferenceSection view={view} />)

    expect(container.querySelector('figure')).toBeNull()
    expect(screen.getByText('Die Brücke schliesst nicht.')).toBeTruthy()
  })

  it('U-04: das Banner steht sichtbar und nennt die Prüfungen', () => {
    render(<QafV2ReferenceSection view={view} />)

    const alerts = screen.getAllByRole('alert')
    expect(alerts.length).toBeGreaterThanOrEqual(1)
    expect(screen.getByText(/P-03/)).toBeTruthy()
    expect(screen.getByText(/gesperrt/)).toBeTruthy()
  })

  it('gibt „nicht geprüft" nicht die Farbe von „in Ordnung"', () => {
    render(<QafV2ReferenceSection view={view} />)

    const ampel = screen.getByText(/Vergleichbarkeit nicht geprüft/)
    expect(ampel.className).not.toMatch(/status-green/)
  })
})
