// @vitest-environment node
// Die Lizenzangaben werden im Main-Prozess von der Platte gelesen. Getestet
// wird gegen echte Dateien in einem temporären Verzeichnis – und einmal gegen
// die echten Dateien des Projekts, damit auch die Pfadauflösung geprüft ist.

import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import type { Katalog } from '../src/shared/katalog';
import { EUPL_HINWEIS, EUPL_KENNUNG, KATALOG_HINWEIS } from '../src/shared/lizenzen';

/**
 * `app.getAppPath()` liefert im Entwicklungsmodus das Verzeichnis mit der
 * package.json – unter Vitest ist das genau das Arbeitsverzeichnis `app/`.
 * Von dort aus findet die Aufwärtssuche die Dateien im Projektwurzelverzeichnis.
 */
vi.mock('electron', () => ({
  app: {
    isPackaged: false,
    getAppPath: (): string => process.cwd(),
  },
}));

const { lizenzenBilden, lizenzenLesen, lizenzenZuruecksetzen } =
  await import('../src/main/lizenzen');

const KATALOG: Katalog = {
  meta: {
    titel: 'Prüfkatalog',
    herausgeber: 'Bundesverwaltungsamt',
    stand: '2024-12-16',
    quellenangabe: 'Amtlicher Fragenkatalog, Stand 16.12.2024.',
    quelle_url: 'https://www.bva.bund.de/',
    quelldatei_sha256: 'a'.repeat(64),
    fragen_gesamt: 1,
  },
  kapitel: [{ id: 'I', titel: 'Waffenrecht', abschnitte: [] }],
  bilder: [],
  fragen: [
    {
      id: 'I-01',
      amtliche_nummer: '1.01',
      kapitel: 'I',
      abschnitt: null,
      typ: 'freitext',
      seite: 1,
      frage: { text: 'Frage', segmente: [{ t: 'Frage' }] },
      bilder: [],
      musterantwort: { text: 'Antwort', segmente: [{ t: 'Antwort', h: true }] },
    },
  ],
};

const DRITTLIZENZEN = {
  stand: 'automatisch erzeugt',
  pakete: [
    {
      name: 'electron',
      version: '43.4.1',
      lizenz: 'MIT',
      url: 'https://github.com/electron/electron',
      lizenztext: 'Copyright (c) Electron contributors …',
    },
    {
      name: 'SQLite',
      version: null,
      lizenz: 'Public Domain',
      url: 'https://www.sqlite.org/copyright.html',
      lizenztext: null,
      hinweis: 'SQLite ist gemeinfrei.',
    },
  ],
};

let verzeichnis: string;
let lizenzPfad: string;
let drittPfad: string;
let warnungen: string[];

function quellen(
  aenderung: Partial<Parameters<typeof lizenzenBilden>[0]> = {},
): Parameters<typeof lizenzenBilden>[0] {
  return {
    lizenzPfad,
    drittPfad,
    katalog: KATALOG,
    warnen: (meldung: string) => warnungen.push(meldung),
    ...aenderung,
  };
}

beforeEach(() => {
  verzeichnis = mkdtempSync(join(tmpdir(), 'waffensachkunde-lizenzen-'));
  lizenzPfad = join(verzeichnis, 'LICENSE.de.txt');
  drittPfad = join(verzeichnis, 'drittlizenzen.json');
  writeFileSync(lizenzPfad, 'EUROPÄISCHE UNION\nÖFFENTLICHE LIZENZ v. 1.2\n', 'utf8');
  writeFileSync(drittPfad, JSON.stringify(DRITTLIZENZEN), 'utf8');
  warnungen = [];
  lizenzenZuruecksetzen();
});

afterEach(() => {
  lizenzenZuruecksetzen();
  rmSync(verzeichnis, { recursive: true, force: true });
});

// ─── Vollständige Angaben ───────────────────────────────────────────────────

describe('lizenzenBilden', () => {
  it('liefert die eigene Lizenz mit deutschem Volltext', () => {
    const angaben = lizenzenBilden(quellen());

    expect(angaben.eigene.kennung).toBe(EUPL_KENNUNG);
    expect(angaben.eigene.hinweis).toBe(EUPL_HINWEIS);
    expect(angaben.eigene.name).toContain('1.2');
    expect(angaben.eigene.text).toContain('ÖFFENTLICHE LIZENZ');
    expect(warnungen).toEqual([]);
  });

  it('führt den Katalog mit Quellenangabe und Rechtshinweis', () => {
    const angaben = lizenzenBilden(quellen());

    expect(angaben.katalog).toEqual({
      quellenangabe: KATALOG.meta.quellenangabe,
      quelleUrl: KATALOG.meta.quelle_url,
      hinweis: KATALOG_HINWEIS,
    });
  });

  it('übernimmt die Fremdkomponenten samt Sammelposten-Hinweis', () => {
    const angaben = lizenzenBilden(quellen());

    expect(angaben.dritte).toHaveLength(2);
    expect(angaben.dritte[0]).toEqual({
      name: 'electron',
      version: '43.4.1',
      lizenz: 'MIT',
      url: 'https://github.com/electron/electron',
      lizenztext: 'Copyright (c) Electron contributors …',
    });
    expect(angaben.dritte[1]).toMatchObject({
      name: 'SQLite',
      version: null,
      lizenztext: null,
      hinweis: 'SQLite ist gemeinfrei.',
    });
  });
});

// ─── Fehlende und beschädigte Dateien ───────────────────────────────────────

describe('Fehlende Angaben', () => {
  it('kommt ohne Lizenzdatei aus und protokolliert das verständlich', () => {
    const angaben = lizenzenBilden(quellen({ lizenzPfad: null }));

    expect(angaben.eigene.text).toBeNull();
    // Die Lizenz selbst gilt weiter – Kennung und Hinweis bleiben stehen.
    expect(angaben.eigene.kennung).toBe(EUPL_KENNUNG);
    expect(angaben.dritte).toHaveLength(2);
    expect(warnungen).toHaveLength(1);
    expect(warnungen[0]).toContain('LICENSE.de.txt');
    expect(warnungen[0]).toMatch(/nicht gefunden/u);
  });

  it('kommt ohne Datei der Fremdkomponenten aus', () => {
    const angaben = lizenzenBilden(quellen({ drittPfad: null }));

    expect(angaben.dritte).toEqual([]);
    expect(angaben.eigene.text).not.toBeNull();
    expect(warnungen[0]).toContain('drittlizenzen.json');
  });

  it('stürzt bei einer nicht vorhandenen Datei nicht ab', () => {
    const angaben = lizenzenBilden(
      quellen({
        lizenzPfad: join(verzeichnis, 'gibt-es-nicht.txt'),
        drittPfad: join(verzeichnis, 'auch-nicht.json'),
      }),
    );

    expect(angaben.eigene.text).toBeNull();
    expect(angaben.dritte).toEqual([]);
    expect(warnungen).toHaveLength(2);
    expect(warnungen.every((w) => w.startsWith('Die Datei'))).toBe(true);
  });

  it('überliest beschädigtes JSON, ohne die übrigen Angaben zu verlieren', () => {
    writeFileSync(drittPfad, '{ das ist kein JSON', 'utf8');

    const angaben = lizenzenBilden(quellen());

    expect(angaben.dritte).toEqual([]);
    expect(angaben.eigene.text).not.toBeNull();
    expect(warnungen).toHaveLength(1);
  });

  it('übergeht einzelne unbrauchbare Einträge und behält die übrigen', () => {
    writeFileSync(
      drittPfad,
      JSON.stringify({
        pakete: [
          { name: 'ohne Lizenzangabe' },
          'gar kein Objekt',
          { name: 'react', version: '19.2.8', lizenz: 'MIT', url: null, lizenztext: null },
        ],
      }),
      'utf8',
    );

    const angaben = lizenzenBilden(quellen());

    expect(angaben.dritte.map((d) => d.name)).toEqual(['react']);
    expect(warnungen).toHaveLength(2);
  });

  it('lässt eine Datei ohne „pakete“ nicht durchgehen', () => {
    writeFileSync(drittPfad, JSON.stringify({ stand: 'leer' }), 'utf8');

    const angaben = lizenzenBilden(quellen());

    expect(angaben.dritte).toEqual([]);
    expect(warnungen[0]).toContain('pakete');
  });

  it('lässt die Katalogangaben leer, wenn der Katalog fehlt', () => {
    const angaben = lizenzenBilden(quellen({ katalog: null }));

    expect(angaben.katalog.quellenangabe).toBe('');
    expect(angaben.katalog.quelleUrl).toBe('');
    // Der Rechtshinweis zum amtlichen Werk gilt unabhängig davon.
    expect(angaben.katalog.hinweis).toBe(KATALOG_HINWEIS);
    expect(warnungen.some((w) => w.includes('Fragenkatalog'))).toBe(true);
  });
});

// ─── Pfadauflösung und Zwischenspeicher ─────────────────────────────────────

describe('lizenzenLesen', () => {
  it('findet die echten Dateien des Projektwurzelverzeichnisses', () => {
    const angaben = lizenzenLesen();

    expect(angaben.eigene.kennung).toBe(EUPL_KENNUNG);
    expect(angaben.eigene.text).toMatch(/EUPL/u);
    expect(angaben.dritte.length).toBeGreaterThan(0);
    expect(angaben.dritte.map((d) => d.name)).toContain('electron');
    expect(angaben.katalog.quellenangabe).toContain('Bundesverwaltungsamt');
    expect(angaben.katalog.quelleUrl).toMatch(/^https:\/\//u);
  });

  it('liest die Dateien nur einmal', () => {
    const erste = lizenzenLesen();
    const zweite = lizenzenLesen();

    expect(zweite).toBe(erste);
  });

  it('liest nach dem Zurücksetzen erneut', () => {
    const erste = lizenzenLesen();
    lizenzenZuruecksetzen();
    const zweite = lizenzenLesen();

    expect(zweite).not.toBe(erste);
    expect(zweite).toEqual(erste);
  });
});
