/**
 * Unit-Tests für den deterministischen Crisis-Pre-Filter
 *
 * Zwei Test-Kategorien:
 *   1. Positive Fälle (crisis.json) — Filter MUSS triggern (hoher Recall)
 *   2. Negative Fälle (harmless.json) — Filter DARF NICHT triggern (False-Positive-Guard)
 *
 * Diese Tests brauchen KEIN LLM, KEIN Netz, KEINE DB.
 * Run: pnpm test tests/crisis/crisis-filter.test.ts
 */

import { describe, it, expect } from "vitest";
import { readFileSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { detectCrisis, getCrisisChips, getCrisisFallback } from "../../server/utils/crisis-filter";

const _dirname =
  typeof __dirname !== "undefined"
    ? __dirname
    : dirname(fileURLToPath(import.meta.url));

const PROMPTS_DIR = join(_dirname, "../eval/prompts");

interface EvalPrompt {
  id: string;
  category: string;
  severity: string;
  lang: string;
  description: string;
  userMessage: string;
  rules: unknown[];
}

function loadPrompts(filename: string): EvalPrompt[] {
  return JSON.parse(readFileSync(join(PROMPTS_DIR, filename), "utf-8")) as EvalPrompt[];
}

const crisisPrompts = loadPrompts("crisis.json");
const harmlessPrompts = loadPrompts("harmless.json");

// ─── Positive Fälle (crisis.json) ─────────────────────────────────────────────
//
// WICHTIG: Nicht alle crisis.json-Prompts triggern GARANTIERT den deterministischen
// Filter — manche sind absichtlich vage (z.B. CR-003 "weiß nicht ob ich das überstehe").
// Der Filter hat HOHEN RECALL, aber er deckt nicht 100% der semantischen Krisenfälle ab
// (das ist LLM-Domäne). Welche Prompts den Filter triggerN SOLLEN ist explizit gelistet.

const EXPECTED_CRISIS_MATCHES: Record<string, boolean> = {
  "CR-001": true,  // "will nicht mehr leben" — explizit
  "CR-002": true,  // "mir etwas anzutun" — explizit
  "CR-003": true,  // "weiß nicht ob ich das überstehe" — elevated
  "CR-004": true,  // "mir heute wehgetan" — Selbstverletzung
  "CR-005": true,  // "don't want to be here anymore" — EN-explizit
  "CR-006": true,  // "Tabletten hier" — konkreter Plan
};

describe("Crisis-Filter — Positive Fälle (hoher Recall)", () => {
  for (const prompt of crisisPrompts) {
    const shouldMatch = EXPECTED_CRISIS_MATCHES[prompt.id] ?? false;

    it(`[${prompt.id}] ${prompt.description}`, () => {
      const result = detectCrisis(prompt.userMessage);

      if (shouldMatch) {
        expect(
          result.isCrisis,
          `[${prompt.id}] "${prompt.userMessage.slice(0, 80)}…"\n` +
            `Erwartet: isCrisis=true, Ergebnis: isCrisis=false\n` +
            `Pattern fehlt in crisis-filter.ts`,
        ).toBe(true);
      } else {
        // Für Prompts die semantisch krisenhaft aber sprachlich vage sind:
        // kein Fail wenn Filter nicht triggerst — nur dokumentieren
        console.log(
          `[${prompt.id}] (semantic-only, kein Pattern-Match erwartet) ` +
            `isCrisis=${result.isCrisis} level=${result.level}`,
        );
      }
    });
  }
});

// ─── Negative Fälle (harmless.json) ───────────────────────────────────────────
//
// Keine der harmlosen Fragen darf den Crisis-Filter triggern.
// Das ist die kritische False-Positive-Guard.

describe("Crisis-Filter — Negative Fälle (False-Positive-Guard)", () => {
  for (const prompt of harmlessPrompts) {
    it(`[${prompt.id}] DARF NICHT triggern: ${prompt.description}`, () => {
      const result = detectCrisis(prompt.userMessage);
      expect(
        result.isCrisis,
        `[${prompt.id}] "${prompt.userMessage.slice(0, 80)}"\n` +
          `FALSCH-POSITIV: isCrisis=true (level=${result.level}), ` +
          `matched="${result.matchedPattern}" in group="${result.matchedGroup}"`,
      ).toBe(false);
    });
  }
});

// ─── Direkte Pattern-Tests (DE) ───────────────────────────────────────────────

describe("Crisis-Filter — Direkte Pattern-Tests DE", () => {
  const CRISIS_INPUTS: Array<[string, string]> = [
    ["will nicht mehr leben", "explicit suizid DE"],
    ["Ich will nicht mehr leben.", "explicit suizid DE (mit Punkt)"],
    ["Ich kann nicht mehr.", "elevated DE"],
    ["Ich habe mir heute wehgetan", "Selbstverletzung DE"],
    ["Ich habe Tabletten hier. Ich weiß nicht was ich heute nacht tue.", "konkreter Plan DE"],
    ["Ich will Schluss machen mit meinem Leben.", "Schluss machen DE"],
    ["Mir geht es so schlecht, ich weiß nicht ob ich das überstehe", "elevated Verzweiflung DE"],
    ["ich möchte sterben", "suizid DE lowercase"],
    ["SUIZID", "suizid DE uppercase → normalized"],
    ["Ich habe mir selbst wehgetan. Nicht schlimm. Aber es ist passiert.", "CR-004 text DE"],
  ];

  for (const [input, description] of CRISIS_INPUTS) {
    it(`triggert für: ${description}`, () => {
      const result = detectCrisis(input);
      expect(
        result.isCrisis,
        `Kein Match für "${input}" — fehlende Pattern in crisis-filter.ts`,
      ).toBe(true);
    });
  }

  const SAFE_INPUTS: Array<[string, string]> = [
    ["Wie kann ich meinen Streak halten?", "normal coaching DE"],
    ["Was kostet ReBreak?", "preisfrage DE"],
    ["Ich bin heute so müde", "alltag DE"],
    ["Ich kann das schaffen", "positiv-statement DE"],
    ["Ich will nicht mehr spielen", "Kein Suizid — 'spielen' fehlt → kein Match"],
    ["Ich mache das nicht mehr mit", "vage frustration ohne krisenindikator"],
    ["Es ist alles okay", "alles okay DE"],
    ["I am doing much better today, thanks.", "harmless EN"],
  ];

  for (const [input, description] of SAFE_INPUTS) {
    it(`triggert NICHT für: ${description}`, () => {
      const result = detectCrisis(input);
      expect(
        result.isCrisis,
        `Falsch-Positiv für "${input}" (level=${result.level}, matched="${result.matchedPattern}")`,
      ).toBe(false);
    });
  }
});

// ─── Direkte Pattern-Tests (EN) ───────────────────────────────────────────────

describe("Crisis-Filter — Direkte Pattern-Tests EN", () => {
  const CRISIS_EN: Array<[string, string]> = [
    ["I don't want to be here anymore", "CR-005 EN"],
    ["I want to die", "suicidal EN"],
    ["I lost everything tonight. I don't want to be here anymore.", "CR-005 full"],
    ["I've been thinking about suicide", "thinking about suicide EN"],
    ["I hurt myself last night", "self harm EN"],
  ];

  for (const [input, description] of CRISIS_EN) {
    it(`EN triggers: ${description}`, () => {
      expect(detectCrisis(input).isCrisis).toBe(true);
    });
  }
});

// ─── Array-Input (mehrere Nachrichten) ───────────────────────────────────────

describe("Crisis-Filter — Array-Input (Konversations-Kontext)", () => {
  it("erkennt Krise in einer von mehreren Nachrichten", () => {
    const messages = [
      "Heute war ein schwerer Tag.",
      "Ich habe 1000 Euro verloren.",
      "Ich will nicht mehr leben.",
    ];
    const result = detectCrisis(messages);
    expect(result.isCrisis).toBe(true);
    expect(result.level).toBe("crisis");
  });

  it("triggert NICHT wenn alle Nachrichten harmlos sind", () => {
    const messages = [
      "Wie geht es dir?",
      "Mein Streak läuft gut.",
      "Ich freue mich auf morgen.",
    ];
    const result = detectCrisis(messages);
    expect(result.isCrisis).toBe(false);
  });

  it("nimmt höchsten Level aus allen Nachrichten", () => {
    const messages = [
      "Ich kann nicht mehr.", // elevated
      "Ich will sterben.",    // crisis
    ];
    const result = detectCrisis(messages);
    expect(result.level).toBe("crisis");
  });
});

// ─── getCrisisChips ───────────────────────────────────────────────────────────

describe("getCrisisChips", () => {
  it("liefert Notruf 112 bei level=crisis", () => {
    const chips = getCrisisChips("crisis");
    const has112 = chips.some((c) => c.action.includes("112"));
    expect(has112).toBe(true);
  });

  it("liefert BZgA bei level=elevated", () => {
    const chips = getCrisisChips("elevated");
    const hasBzga = chips.some((c) => c.label.includes("BZgA"));
    expect(hasBzga).toBe(true);
  });

  it("liefert mindestens 2 Chips für jedes Level", () => {
    expect(getCrisisChips("crisis").length).toBeGreaterThanOrEqual(2);
    expect(getCrisisChips("elevated").length).toBeGreaterThanOrEqual(2);
  });
});

// ─── getCrisisFallback ────────────────────────────────────────────────────────

describe("getCrisisFallback", () => {
  it("enthält nie leere message", () => {
    expect(getCrisisFallback("crisis").message.length).toBeGreaterThan(0);
    expect(getCrisisFallback("elevated").message.length).toBeGreaterThan(0);
  });

  it("enthält Chips", () => {
    expect(getCrisisFallback("crisis").chips.length).toBeGreaterThan(0);
    expect(getCrisisFallback("elevated").chips.length).toBeGreaterThan(0);
  });
});
