import "server-only";
import mysql from "mysql2/promise";

// ──────────────────────────────────────────────────────────────────────────
// Zweite, unabhängige Verbindung zur GEMEINSAMEN Formular-Datenbank.
//
// Diese Datenbank gehört nicht dem Team OS allein — die öffentliche Website
// arbeitet auf denselben Tabellen. Deshalb bewusst kein Prisma: So kann
// `prisma db push` die fremden Tabellen unter keinen Umständen verändern.
// Zugriff ausschließlich über Prepared Statements.
//
// Konfiguration in der .env:
//   FORMS_DATABASE_URL="mysql://benutzer:passwort@host:3306/datenbank"
// ──────────────────────────────────────────────────────────────────────────

let pool: mysql.Pool | null = null;
let configWarned = false;

/** Erlaubte Platzhalterwerte. Booleans werden bewusst als 1/0 übergeben. */
export type FormsParam = string | number | null;

export function isFormsConfigured(): boolean {
  return Boolean(process.env.FORMS_DATABASE_URL?.trim());
}

type FormsConnection = {
  host: string;
  port: number;
  user: string;
  password: string;
  database: string;
};

/** Prozentkodierung auflösen, aber nur wenn sie auch aufgeht. */
function maybeDecode(value: string): string {
  if (!value.includes("%")) return value;
  try {
    return decodeURIComponent(value);
  } catch {
    return value;
  }
}

/**
 * Verbindungszeichenfolge selbst zerlegen statt sie an `new URL()` zu geben.
 *
 * Datenbankpasswörter enthalten regelmäßig `/`, `@`, `[` oder `#`. Als URL
 * gelesen zerbricht die Zeichenfolge daran ("Invalid URL"), obwohl die Angabe
 * völlig in Ordnung ist. Das Passwort ist deshalb alles zwischen dem ersten
 * `:` und dem LETZTEN `@` — nur Doppelpunkt und Klammeraffe müssen im
 * Passwort selbst kodiert werden, alles andere darf so stehen bleiben.
 */
export function parseFormsUrl(raw: string): FormsConnection | null {
  const value = raw.trim().replace(/^["']|["']$/g, "");
  const match = value.match(
    /^(?:mysql|mariadb):\/\/([^:@]+)(?::([\s\S]*))?@([^@:/]+)(?::(\d+))?\/([^?]+)(?:\?.*)?$/,
  );
  if (!match) return null;

  const [, user, password, host, port, database] = match;
  if (!user || !host || !database) return null;

  return {
    host,
    port: port ? Number(port) : 3306,
    user: maybeDecode(user),
    password: maybeDecode(password ?? ""),
    database: maybeDecode(database),
  };
}

function getPool(): mysql.Pool | null {
  const raw = process.env.FORMS_DATABASE_URL?.trim();
  if (!raw) {
    if (!configWarned) {
      configWarned = true;
      console.warn(
        "[forms] FORMS_DATABASE_URL ist nicht gesetzt — Ausschreibungen, Bewerbungen und Kontaktanfragen bleiben leer.",
      );
    }
    return null;
  }

  const conn = parseFormsUrl(raw);
  if (!conn) {
    if (!configWarned) {
      configWarned = true;
      console.error(
        "[forms] FORMS_DATABASE_URL konnte nicht gelesen werden. Erwartet: mysql://benutzer:passwort@host:3306/datenbank",
      );
    }
    return null;
  }

  if (!pool) {
    pool = mysql.createPool({
      ...conn,
      charset: "utf8mb4_unicode_ci",
      connectionLimit: 5,
      waitForConnections: true,
      // Datum als String übernehmen — die Anzeige formatiert selbst und es
      // gibt keine Zeitzonen-Verschiebung zwischen den beiden Websites.
      dateStrings: true,
    });
  }
  return pool;
}

/**
 * Lesender Zugriff. Ist die gemeinsame Datenbank nicht erreichbar, liefert er
 * den Ersatzwert zurück — das Team OS läuft dann ohne diese Bereiche weiter.
 */
export async function formsQuery<T>(
  sql: string,
  params: FormsParam[],
  fallback: T[],
): Promise<T[]> {
  const p = getPool();
  if (!p) return fallback;
  try {
    const [rows] = await p.execute(sql, params);
    return rows as T[];
  } catch (err) {
    console.error("[forms] Lesefehler:", err instanceof Error ? err.message : err);
    return fallback;
  }
}

/**
 * Schreibender Zugriff. Fehler werden bewusst NICHT geschluckt: Ein „Danke",
 * hinter dem nichts gespeichert wurde, ist der schlechteste Ausgang.
 */
export async function formsExecute(
  sql: string,
  params: FormsParam[] = [],
): Promise<mysql.ResultSetHeader> {
  const p = getPool();
  if (!p) {
    throw new Error(
      "Die gemeinsame Formular-Datenbank ist nicht konfiguriert (FORMS_DATABASE_URL).",
    );
  }
  const [result] = await p.execute(sql, params);
  return result as mysql.ResultSetHeader;
}

/** Erreichbarkeit prüfen — für Statusanzeige und Diagnose. */
export async function formsHealth(): Promise<{ ok: boolean; error?: string }> {
  if (!isFormsConfigured()) {
    return { ok: false, error: "FORMS_DATABASE_URL ist nicht gesetzt." };
  }
  try {
    const p = getPool();
    if (!p) {
      return {
        ok: false,
        error:
          "FORMS_DATABASE_URL konnte nicht gelesen werden. Erwartet: mysql://benutzer:passwort@host:3306/datenbank",
      };
    }
    await p.query("SELECT 1");
    return { ok: true };
  } catch (err) {
    return { ok: false, error: err instanceof Error ? err.message : "Unbekannter Fehler" };
  }
}

/**
 * Existenzprüfung über `information_schema`.
 * `SHOW TABLES LIKE ?` funktioniert mit echten Prepared Statements nicht.
 */
export async function formsTablesExist(names: string[]): Promise<Set<string>> {
  const rows = await formsQuery<{ TABLE_NAME: string }>(
    `SELECT TABLE_NAME FROM information_schema.tables
      WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (${names.map(() => "?").join(",")})`,
    names,
    [],
  );
  return new Set(rows.map((r) => r.TABLE_NAME));
}
