/**
 * Testet den Einstellungen-Reiter des Studios ohne Browser.
 *
 * Die Methoden werden aus studio.js herausgeloest und gegen ein schlankes
 * DOM-Double laufen gelassen. Kein Netzzugriff, keine Kosten.
 *
 * Aufruf aus dem Projektstamm:
 *     node tests/test_falleinstellungen.js
 */
'use strict';

const fs = require('fs');
const path = require('path');
const vm = require('vm');

let ok = 0;
let fail = 0;

function pruefe(name, bedingung, extra) {
    if (bedingung) {
        ok++;
        console.log('  OK    ' + name);
    } else {
        fail++;
        console.log('  FEHL  ' + name + ' '
            + (extra === undefined ? '' : JSON.stringify(extra)));
    }
}

// --- DOM-Double ------------------------------------------------------------
function element(id) {
    const klassen = new Set();
    return {
        id: id,
        value: '',
        checked: false,
        textContent: '',
        innerHTML: '',
        hidden: false,
        disabled: false,
        min: 0,
        _fokus: 0,
        focus() { this._fokus++; },
        classList: {
            toggle(name, an) { if (an) klassen.add(name); else klassen.delete(name); },
            contains(name) { return klassen.has(name); },
        },
    };
}

const FELDER = [
    'set-title', 'set-description', 'set-international', 'set-telegram', 'set-x',
    'set-x-hint', 'set-visibility', 'set-visibility-text', 'set-ai-backend',
    'set-ai-warn', 'set-refresh-mode', 'set-refresh-value', 'set-refresh-unit',
    'set-interval-field', 'set-starttime-field', 'set-refresh-starttime',
    'set-retention', 'set-notify-summary', 'set-notify-new-articles',
    'set-notify-status-change', 'set-save', 'set-state', 'set-running-note',
];

const elemente = {};
const meldungen = [];
const aufrufe = { update: [], abo: [] };
let aboAntwort = null;
let aboWirft = false;
let updateWirft = false;
let xKonten = [{ active: true }];

const umgebung = {
    document: { getElementById: (id) => elemente[id] || null },
    UI: { showToast: (text, art) => meldungen.push({ text, art }) },
    API: {
        getSubscription: async () => {
            if (aboWirft) throw new Error('kein Abo');
            return aboAntwort;
        },
        updateIncident: async (id, daten) => {
            aufrufe.update.push({ id, daten });
            if (updateWirft) throw new Error('keine Berechtigung');
            return Object.assign({ id }, daten);
        },
        updateSubscription: async (id, daten) => { aufrufe.abo.push({ id, daten }); },
        listXAccounts: async () => xKonten,
    },
    console: console,
};
umgebung.window = umgebung;

// --- Die Methoden aus studio.js herausloesen -------------------------------
const quelle = fs.readFileSync(
    path.join(__dirname, '..', 'src', 'static', 'js', 'studio.js'), 'utf8');
const von = quelle.indexOf('    // === Einstellungen des Falls ===');
const bis = quelle.indexOf('    _tabTitle(key) {', von);
if (von === -1 || bis === -1) {
    console.log('FEHLER: Einstellungen-Block in studio.js nicht gefunden');
    process.exit(1);
}

const kontext = vm.createContext(umgebung);
vm.runInContext('var Studio = {\n' + quelle.slice(von, bis) + '\n'
    + '  incident: null, incidents: [], _runningStage: null,\n'
    + '  _kopf: 0, _liste: 0,\n'
    + '  renderHeader() { this._kopf++; },\n'
    + '  renderCases() { this._liste++; },\n'
    + '};', kontext);
const Studio = kontext.Studio;

const LAGE = {
    id: 7,
    title: 'Ceuta',
    description: 'Grenzsituation',
    type: 'adhoc',
    status: 'active',
    visibility: 'public',
    international_sources: true,
    include_telegram: false,
    include_x: false,
    ai_backend: 'bedrock',
    refresh_mode: 'auto',
    refresh_interval: 1440,
    refresh_start_time: '06:30',
    retention_days: 30,
};

function neu(ueber) {
    for (const id of FELDER) elemente[id] = element(id);
    Studio.incident = Object.assign({}, LAGE, ueber || {});
    Studio.incidents = [Object.assign({}, Studio.incident)];
    Studio._runningStage = null;
    Studio._settingsAlt = null;
    Studio._settingsAboAlt = null;
    Studio._kopf = 0;
    Studio._liste = 0;
    aufrufe.update.length = 0;
    aufrufe.abo.length = 0;
    meldungen.length = 0;
    aboAntwort = null;
    aboWirft = false;
    updateWirft = false;
    xKonten = [{ active: true }];
}

const g = (id) => elemente[id];

async function main() {
    console.log('\nA) Das Formular wird aus dem Fall gefüllt');
    neu();
    await Studio.loadSettings();
    pruefe('A1 Titel', g('set-title').value === 'Ceuta');
    pruefe('A2 Beschreibung', g('set-description').value === 'Grenzsituation');
    pruefe('A3 internationale Quellen an', g('set-international').checked === true);
    pruefe('A4 Telegram aus', g('set-telegram').checked === false);
    pruefe('A5 öffentlich', g('set-visibility').checked === true);
    pruefe('A6 KI-Weg steht auf EU', g('set-ai-backend').value === 'bedrock');
    pruefe('A7 Aufbewahrung', String(g('set-retention').value) === '30');
    pruefe('A8 Startzeit', g('set-refresh-starttime').value === '06:30');

    console.log('\nB) Das Intervall wird in der größten glatten Einheit gezeigt');
    pruefe('B1 1440 Minuten werden zu 1 Tag',
           g('set-refresh-unit').value === '1440' && String(g('set-refresh-value').value) === '1',
           [g('set-refresh-unit').value, g('set-refresh-value').value]);
    neu({ refresh_interval: 90 });
    await Studio.loadSettings();
    pruefe('B2 90 Minuten bleiben Minuten',
           g('set-refresh-unit').value === '1' && String(g('set-refresh-value').value) === '90',
           [g('set-refresh-unit').value, g('set-refresh-value').value]);
    neu({ refresh_interval: 120 });
    await Studio.loadSettings();
    pruefe('B3 120 Minuten werden zu 2 Stunden',
           g('set-refresh-unit').value === '60' && String(g('set-refresh-value').value) === '2');
    neu({ refresh_interval: 10080 });
    await Studio.loadSettings();
    pruefe('B4 10080 Minuten werden zu 1 Woche',
           g('set-refresh-unit').value === '10080' && String(g('set-refresh-value').value) === '1');

    console.log('\nC) Intervall und Startzeit nur bei automatischer Aktualisierung');
    neu({ refresh_mode: 'manual' });
    await Studio.loadSettings();
    pruefe('C1 bei manuell verborgen',
           g('set-interval-field').classList.contains('visible') === false
           && g('set-starttime-field').classList.contains('visible') === false);
    g('set-refresh-mode').value = 'auto';
    Studio.settingsRefreshToggle();
    pruefe('C2 bei automatisch sichtbar',
           g('set-interval-field').classList.contains('visible') === true
           && g('set-starttime-field').classList.contains('visible') === true);

    console.log('\nD) Gespeichert wird nur, was sich geändert hat');
    neu();
    await Studio.loadSettings();
    await Studio.saveSettings();
    pruefe('D1 ohne Änderung kein Aufruf', aufrufe.update.length === 0, aufrufe.update);
    pruefe('D2 der Zustand sagt es', g('set-state').textContent === 'Nichts geändert',
           g('set-state').textContent);

    g('set-title').value = 'Ceuta und Melilla';
    await Studio.saveSettings();
    pruefe('D3 genau ein Aufruf', aufrufe.update.length === 1);
    pruefe('D4 nur der Titel im Patch',
           JSON.stringify(Object.keys(aufrufe.update[0].daten)) === '["title"]',
           aufrufe.update[0].daten);
    pruefe('D5 richtige Lage', aufrufe.update[0].id === 7);
    pruefe('D6 Liste und Kopfzeile neu gezeichnet',
           Studio._kopf === 1 && Studio._liste === 1);
    pruefe('D7 Rückmeldung an den Nutzer',
           meldungen.length === 1 && meldungen[0].art === 'success', meldungen);

    await Studio.saveSettings();
    pruefe('D8 direkt danach ist wieder nichts offen', aufrufe.update.length === 1,
           aufrufe.update);

    console.log('\nE) Der KI-Weg lässt sich umstellen');
    neu();
    await Studio.loadSettings();
    g('set-ai-backend').value = 'cli';
    await Studio.saveSettings();
    pruefe('E1 nur der KI-Weg im Patch',
           JSON.stringify(aufrufe.update[0].daten) === '{"ai_backend":"cli"}',
           aufrufe.update[0].daten);

    neu();
    await Studio.loadSettings();
    g('set-ai-backend').value = '';
    await Studio.saveSettings();
    pruefe('E2 Zurücksetzen auf die Org-Vorgabe schickt den Leerstring',
           aufrufe.update.length === 1 && aufrufe.update[0].daten.ai_backend === '',
           aufrufe.update[0] && aufrufe.update[0].daten);

    console.log('\nF) Der Warnhinweis folgt der Änderung');
    neu();
    await Studio.loadSettings();
    pruefe('F1 zunächst verborgen', g('set-ai-warn').hidden === true);
    g('set-ai-backend').value = 'cli';
    Studio.settingsAiChanged();
    pruefe('F2 nach der Umstellung sichtbar', g('set-ai-warn').hidden === false);
    g('set-ai-backend').value = 'bedrock';
    Studio.settingsAiChanged();
    pruefe('F3 zurückgestellt wieder verborgen', g('set-ai-warn').hidden === true);
    g('set-ai-backend').value = 'cli';
    Studio.settingsAiChanged();
    await Studio.saveSettings();
    pruefe('F4 nach dem Speichern ist der neue Weg der Normalfall',
           g('set-ai-warn').hidden === true);

    console.log('\nG) Der Hinweis auf einen laufenden Schritt');
    neu();
    Studio._runningStage = 'collect';
    await Studio.loadSettings();
    pruefe('G1 bei laufendem Schritt sichtbar', g('set-running-note').hidden === false);
    neu();
    await Studio.loadSettings();
    pruefe('G2 sonst verborgen', g('set-running-note').hidden === true);

    console.log('\nH) E-Mail-Benachrichtigungen');
    neu();
    aboAntwort = { notify_email_summary: true, notify_email_new_articles: false,
                   notify_email_status_change: true };
    await Studio.loadSettings();
    pruefe('H1 Stand wird geladen',
           g('set-notify-summary').checked === true
           && g('set-notify-new-articles').checked === false
           && g('set-notify-status-change').checked === true);
    await Studio.saveSettings();
    pruefe('H2 ohne Änderung kein Abo-Aufruf', aufrufe.abo.length === 0);
    g('set-notify-new-articles').checked = true;
    await Studio.saveSettings();
    pruefe('H3 Änderung wird geschickt', aufrufe.abo.length === 1);
    pruefe('H4 alle drei Werte im Aufruf',
           aufrufe.abo[0].daten.notify_email_new_articles === true
           && aufrufe.abo[0].daten.notify_email_summary === true,
           aufrufe.abo[0].daten);
    pruefe('H5 die Lage selbst wurde nicht angefasst', aufrufe.update.length === 0);

    neu();
    aboWirft = true;
    await Studio.loadSettings();
    pruefe('H6 ohne Abo bleiben die Haken aus',
           g('set-notify-summary').checked === false
           && g('set-notify-status-change').checked === false);

    console.log('\nI) Randfälle');
    neu();
    await Studio.loadSettings();
    g('set-title').value = '   ';
    await Studio.saveSettings();
    pruefe('I1 ohne Titel wird nicht gespeichert', aufrufe.update.length === 0);
    pruefe('I2 Fehlermeldung erscheint',
           meldungen.length === 1 && meldungen[0].art === 'error', meldungen);
    pruefe('I3 das Titelfeld bekommt den Fokus', g('set-title')._fokus === 1);

    neu();
    await Studio.loadSettings();
    updateWirft = true;
    g('set-title').value = 'Anderer Titel';
    await Studio.saveSettings();
    pruefe('I4 Fehler beim Speichern wird gemeldet',
           meldungen.some(m => m.art === 'error'), meldungen);
    pruefe('I5 der Zustand sagt es auch', g('set-state').textContent === 'Nicht gespeichert',
           g('set-state').textContent);
    pruefe('I6 der Knopf ist wieder bedienbar', g('set-save').disabled === false);

    neu();
    Studio.incident = null;
    await Studio.loadSettings();
    await Studio.saveSettings();
    pruefe('I7 ohne offenen Fall passiert nichts', aufrufe.update.length === 0);

    console.log('\nJ) Der X-Schalter hängt an den hinterlegten Zugängen');
    neu();
    xKonten = [];
    await Studio.loadSettings();
    pruefe('J1 ohne Zugang gesperrt', g('set-x').disabled === true);
    pruefe('J2 mit Hinweis', g('set-x-hint').hidden === false);
    neu({ include_x: true });
    xKonten = [];
    await Studio.loadSettings();
    pruefe('J3 nutzt der Fall X bereits, bleibt er bedienbar', g('set-x').disabled === false);
    neu();
    xKonten = [{ active: true }];
    await Studio.loadSettings();
    pruefe('J4 mit Zugang frei und ohne Hinweis',
           g('set-x').disabled === false && g('set-x-hint').hidden === true);

    console.log('\nK) Die Sichtbarkeit beschriftet sich selbst');
    neu();
    await Studio.loadSettings();
    pruefe('K1 öffentlich', g('set-visibility-text').textContent.startsWith('Öffentlich'),
           g('set-visibility-text').textContent);
    g('set-visibility').checked = false;
    Studio.settingsVisibilityHint();
    pruefe('K2 privat', g('set-visibility-text').textContent.startsWith('Privat'),
           g('set-visibility-text').textContent);

    console.log('\nL) Das Kennzeichen des KI-Wegs');
    pruefe('L1 EU', Studio._aiTag({ ai_backend: 'bedrock' }, 'x').includes('>EU<'));
    pruefe('L2 EU trägt die Klasse', Studio._aiTag({ ai_backend: 'bedrock' }, 'x').includes('x ai-eu'));
    pruefe('L3 Anthropic', Studio._aiTag({ ai_backend: 'cli' }, 'x').includes('>Anthropic<'));
    pruefe('L4 ohne Angabe bleibt es leer', Studio._aiTag({ ai_backend: null }, 'x') === '');
    pruefe('L5 auch ohne Lage', Studio._aiTag(null, 'x') === '');
    pruefe('L6 unbekannter Wert erzeugt nichts',
           Studio._aiTag({ ai_backend: 'irgendwas' }, 'x') === '');

    console.log('\nM) Die Untergrenze des Intervalls');
    neu();
    await Studio.loadSettings();
    g('set-refresh-unit').value = '1';
    g('set-refresh-value').value = '3';
    Studio.settingsIntervalMin();
    pruefe('M1 in Minuten sind 10 das Minimum', String(g('set-refresh-value').value) === '10',
           g('set-refresh-value').value);
    g('set-refresh-unit').value = '60';
    g('set-refresh-value').value = '1';
    Studio.settingsIntervalMin();
    pruefe('M2 in Stunden ist 1 erlaubt', String(g('set-refresh-value').value) === '1');

    console.log('\nErgebnis: ' + ok + ' bestanden, ' + fail + ' fehlgeschlagen');
    process.exit(fail ? 1 : 0);
}

main();
