import { describe, it, expect } from "vitest"
import type { CoverageEntry } from "@/lib/qaf-differences"
import {
  QAF_SECTION_TITLES,
  type QafSectionKey,
} from "@/components/qaf-differences/qaf-section-registry"
import {
  PRIMARY_AREAS,
  ALL_SECTIONS_AREA,
  PRIMARY_AREA_PARAM,
  visibleAreas,
  resolveActiveArea,
  sectionVisible,
  mergeCoverage,
  areaTabHref,
} from "@/components/qaf-differences/qaf-primary-navigation"

function entry(module: string, status: CoverageEntry["status"]): CoverageEntry {
  return {
    module: module as CoverageEntry["module"],
    labelDe: module,
    status,
    statusLabelDe: String(status),
  }
}

const ALL_PRESENT: CoverageEntry[] = [
  "SUMMARY",
  "MANUFACTURING",
  "MATERIAL",
  "SBM",
  "LOGISTICS",
  "RMR",
  "LC_CN",
  "CO2E",
].map((m) => entry(m, "AVAILABLE"))

describe("PRIMARY_AREAS — Roadmap-Terminologie und Sektions-Abdeckung", () => {
  it("führt die von der Roadmap vorgegebenen Hauptbereiche", () => {
    const labels = PRIMARY_AREAS.map((a) => a.label)
    expect(labels).toContain("Überblick")
    expect(labels).toContain("Zusammenfassung")
    expect(labels).toContain("Material")
    expect(labels).toContain("Fertigungskosten")
    expect(labels).toContain("Rohstoffrisiken")
    expect(labels).toContain("Logistik und Zoll")
  })

  it("vergibt eindeutige Bezeichner, keiner kollidiert mit der Kompatibilitätsansicht", () => {
    const ids = PRIMARY_AREAS.map((a) => a.id)
    expect(new Set(ids).size).toBe(ids.length)
    expect(ids).not.toContain(ALL_SECTIONS_AREA)
  })

  // Der eigentliche Regressionsschutz: verschwindet eine Sektion beim
  // Zuordnen, sieht ein Nutzer im Reiter-Modus weniger als vorher, ohne dass
  // es jemand merkt. Jede der 14 muss genau einen Bereich haben.
  it("ordnet jede der 14 Sektionen genau einem Bereich zu — keine verliert sich", () => {
    const alle = Object.keys(QAF_SECTION_TITLES) as QafSectionKey[]
    expect(alle.length).toBe(14)

    const zugeordnet = PRIMARY_AREAS.flatMap((a) => a.sections)
    expect(new Set(zugeordnet).size).toBe(zugeordnet.length) // keine doppelt

    const fehlend = alle.filter((k) => !zugeordnet.includes(k))
    expect(fehlend).toEqual([])
  })

  it("kennt Bereiche ohne eigene Sektion und versteckt sie nicht deswegen", () => {
    // Material hat auf der Langseite keine eigene Sektion — der Bereich muss
    // trotzdem existieren, sonst wäre "nicht gebaut" von "nicht vorhanden"
    // nicht mehr unterscheidbar.
    const material = PRIMARY_AREAS.find((a) => a.id === "material")!
    expect(material.sections).toEqual([])
    expect(material.modules).toContain("MATERIAL")
    expect(visibleAreas(ALL_PRESENT).map((a) => a.id)).toContain("material")
  })
})

describe("visibleAreas — nur verfügbare Hauptreiter", () => {
  it("zeigt alles, wenn die Matrix alle Module als vorhanden meldet", () => {
    expect(visibleAreas(ALL_PRESENT).length).toBe(PRIMARY_AREAS.length)
  })

  it("blendet einen Bereich aus, dessen Modul fehlt", () => {
    const ohneMaterial = ALL_PRESENT.map((e) =>
      e.module === "MATERIAL" ? entry("MATERIAL", "MISSING") : e
    )
    const ids = visibleAreas(ohneMaterial).map((a) => a.id)
    expect(ids).not.toContain("material")
    expect(ids).toContain("zusammenfassung")
  })

  it("behält modul-unabhängige Bereiche auch bei komplett leerer Matrix", () => {
    const ids = visibleAreas([]).map((a) => a.id)
    expect(ids).toContain("ueberblick")
    expect(ids).toContain("massnahmen")
    expect(ids).not.toContain("material")
  })

  // Gegenprobe zur Ehrlichkeitsregel aus coverage-view.ts: "nie erhoben" ist
  // etwas anderes als "geprüft und nicht da".
  it("zeigt bei fehlendem Capability-Stand (null) ALLE Bereiche statt keinen", () => {
    expect(visibleAreas(null)).toEqual(PRIMARY_AREAS)
  })

  it("zählt PARSE_FAILED als vorhanden — das Blatt ist da, nur unlesbar", () => {
    const kaputt = [entry("MATERIAL", "PARSE_FAILED")]
    expect(visibleAreas(kaputt).map((a) => a.id)).toContain("material")
  })

  it("zählt PARTIAL und DERIVABLE als vorhanden", () => {
    expect(visibleAreas([entry("RMR", "PARTIAL")]).map((a) => a.id)).toContain(
      "rohstoffrisiken"
    )
    expect(
      visibleAreas([entry("CO2E", "DERIVABLE")]).map((a) => a.id)
    ).toContain("zusatzmodule")
  })

  it('zeigt "Logistik und Zoll", wenn auch nur eines seiner beiden Module trägt', () => {
    expect(
      visibleAreas([entry("LC_CN", "AVAILABLE")]).map((a) => a.id)
    ).toContain("logistik")
    expect(
      visibleAreas([
        entry("LOGISTICS", "MISSING"),
        entry("LC_CN", "MISSING"),
      ]).map((a) => a.id)
    ).not.toContain("logistik")
  })
})

describe("resolveActiveArea — Deep-Link, gegen die sichtbaren Reiter geprüft", () => {
  const sichtbar = visibleAreas(ALL_PRESENT)

  it("nimmt einen gültigen, sichtbaren Bereich", () => {
    expect(resolveActiveArea("fertigungskosten", sichtbar)).toBe(
      "fertigungskosten"
    )
  })

  it("fällt ohne Parameter auf die Kompatibilitätsansicht — Bestandsverhalten", () => {
    expect(resolveActiveArea(undefined, sichtbar)).toBe(ALL_SECTIONS_AREA)
  })

  it("akzeptiert die Kompatibilitätsansicht ausdrücklich", () => {
    expect(resolveActiveArea(ALL_SECTIONS_AREA, sichtbar)).toBe(
      ALL_SECTIONS_AREA
    )
  })

  // Der Fall, der ohne Prüfung einen leeren Reiter erzeugt hätte: ein alter
  // Link auf einen Bereich, den DIESE Datei nicht trägt.
  it("erzwingt keinen Bereich, den die Matrix ausgeblendet hat", () => {
    const ohneMaterial = visibleAreas(
      ALL_PRESENT.map((e) =>
        e.module === "MATERIAL" ? entry("MATERIAL", "MISSING") : e
      )
    )
    expect(resolveActiveArea("material", ohneMaterial)).toBe(ALL_SECTIONS_AREA)
  })

  it("fällt bei Unsinn, leerem Wert und geerbten Namen zurück", () => {
    expect(resolveActiveArea("gibtsnicht", sichtbar)).toBe(ALL_SECTIONS_AREA)
    expect(resolveActiveArea("", sichtbar)).toBe(ALL_SECTIONS_AREA)
    expect(resolveActiveArea("__proto__", sichtbar)).toBe(ALL_SECTIONS_AREA)
    expect(resolveActiveArea("toString", sichtbar)).toBe(ALL_SECTIONS_AREA)
  })

  it("nimmt bei Mehrfachangabe den ersten gültigen Wert", () => {
    expect(resolveActiveArea(["quatsch", "material"], sichtbar)).toBe(
      "material"
    )
    expect(resolveActiveArea([], sichtbar)).toBe(ALL_SECTIONS_AREA)
  })
})

describe("sectionVisible — die Kompatibilitätsansicht bleibt vollständig", () => {
  it("zeigt in der Kompatibilitätsansicht jede der 14 Sektionen", () => {
    for (const key of Object.keys(QAF_SECTION_TITLES) as QafSectionKey[]) {
      expect(sectionVisible(ALL_SECTIONS_AREA, key)).toBe(true)
    }
  })

  it("zeigt im Bereich nur dessen eigene Sektionen", () => {
    expect(sectionVisible("fertigungskosten", "prod")).toBe(true)
    expect(sectionVisible("fertigungskosten", "buckets")).toBe(false)
    expect(sectionVisible("zusammenfassung", "buckets")).toBe(true)
    expect(sectionVisible("ueberblick", "fazit")).toBe(true)
  })

  it("zeigt bei unbekanntem Bereich lieber alles als nichts", () => {
    expect(sectionVisible("gibtsnicht", "metrics")).toBe(true)
  })
})

describe("mergeCoverage — beide Vergleichsseiten", () => {
  it("bietet einen Bereich an, sobald EINE Seite ihn trägt", () => {
    // Genau der Fall, den ein Vergleich zeigen soll: Modul nur im neuen Stand.
    const merged = mergeCoverage(
      [entry("MATERIAL", "MISSING")],
      [entry("MATERIAL", "AVAILABLE")]
    )
    expect(visibleAreas(merged).map((a) => a.id)).toContain("material")
  })

  it("ist reihenfolgeunabhängig — der vorhandene Stand gewinnt in beide Richtungen", () => {
    const a = mergeCoverage(
      [entry("RMR", "AVAILABLE")],
      [entry("RMR", "MISSING")]
    )
    const b = mergeCoverage(
      [entry("RMR", "MISSING")],
      [entry("RMR", "AVAILABLE")]
    )
    expect(visibleAreas(a).map((x) => x.id)).toContain("rohstoffrisiken")
    expect(visibleAreas(b).map((x) => x.id)).toContain("rohstoffrisiken")
  })

  it("führt jedes Modul genau einmal", () => {
    const merged = mergeCoverage(ALL_PRESENT, ALL_PRESENT)!
    expect(merged.length).toBe(new Set(merged.map((e) => e.module)).size)
    expect(merged.length).toBe(8)
  })

  it('bleibt null, wenn keine Seite einen Stand hat — "nie gemessen" ist keine leere Liste', () => {
    expect(mergeCoverage(null, null)).toBeNull()
  })

  it("nimmt die eine Seite, wenn nur sie einen Stand hat", () => {
    expect(mergeCoverage(null, [entry("SBM", "AVAILABLE")])).toEqual([
      entry("SBM", "AVAILABLE"),
    ])
    expect(mergeCoverage([entry("SBM", "AVAILABLE")], null)).toEqual([
      entry("SBM", "AVAILABLE"),
    ])
  })
})

describe("areaTabHref", () => {
  it("benutzt denselben Parameter, den resolveActiveArea liest", () => {
    const href = areaTabHref("material")
    const value = new URLSearchParams(href.slice(1)).get(PRIMARY_AREA_PARAM)
    expect(value).toBe("material")
    expect(
      resolveActiveArea(value ?? undefined, visibleAreas(ALL_PRESENT))
    ).toBe("material")
  })
})
