import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { sql } from "drizzle-orm";
import { Pool } from "pg";
import * as schema from "@/db/schema";

/**
 * Live-Datenbank-Tests (Postgres erforderlich).
 *
 * Diese Tests werden ÜBERSPRUNGEN, wenn keine erreichbare Datenbank vorhanden
 * ist (z. B. wenn DATABASE_URL fehlt oder der Connect fehlschlägt). Bei
 * vorhandener DB prüfen sie Enums, Indizes, den partiellen Unique-Index, den
 * EAV-Round-Trip und FK-Verletzungen anhand echter SQL-Ausführung.
 */

const url = process.env.DATABASE_URL;
let pool: Pool | undefined;
let db: ReturnType<typeof drizzle> | undefined;
let dbReachable = false;

beforeAll(async () => {
  if (!url) return;
  try {
    pool = new Pool({ connectionString: url, max: 1, connectionTimeoutMillis: 2000 });
    const client = await pool.connect();
    client.release();
    db = drizzle(pool, { schema });
    await migrate(db, { migrationsFolder: "./drizzle" });
    dbReachable = true;
  } catch {
    dbReachable = false;
    if (pool) await pool.end().catch(() => {});
    pool = undefined;
  }
});

afterAll(async () => {
  if (pool) await pool.end().catch(() => {});
});

const dbIt: typeof it = ((...args: Parameters<typeof it>) =>
  (dbReachable ? it : it.skip)(...args)) as typeof it;

describe("Live-DB: Schema", () => {
  dbIt("asset_status enthält die drei Werte", async () => {
    const res = await db!.execute(
      sql`SELECT enum_range(NULL::asset_status)::text AS r`,
    );
    const r = (res.rows[0] as { r: string }).r;
    for (const v of ["einsatzbereit", "wartung", "ausser_dienst"]) {
      expect(r).toContain(v);
    }
  });

  dbIt("alle vier mv_*-Indizes existieren", async () => {
    const res = await db!.execute(
      sql`SELECT indexname FROM pg_indexes WHERE tablename = 'merkmal_values'`,
    );
    const names = new Set(res.rows.map((r) => (r as { indexname: string }).indexname));
    for (const idx of [
      "mv_merkmal_num_idx",
      "mv_merkmal_bool_idx",
      "mv_merkmal_text_idx",
      "mv_entity_idx",
    ]) {
      expect(names).toContain(idx);
    }
  });

  dbIt("merkmale_active_name_uq ist partiell (WHERE status='active')", async () => {
    const res = await db!.execute(
      sql`SELECT indexdef FROM pg_indexes WHERE indexname = 'merkmale_active_name_uq'`,
    );
    expect(res.rows.length).toBe(1);
    const def = (res.rows[0] as { indexdef: string }).indexdef.toLowerCase();
    expect(def).toContain("unique");
    expect(def).toContain("where");
    expect(def).toContain("active");
  });

  dbIt("login_attempts_key_zeit_idx existiert", async () => {
    const res = await db!.execute(
      sql`SELECT 1 FROM pg_indexes WHERE indexname = 'login_attempts_key_zeit_idx'`,
    );
    expect(res.rows.length).toBe(1);
  });

  dbIt(
    "zwei active-Merkmale gleichen Namens scheitern, zwei proposed gelingen",
    async () => {
      const base = `roundtrip_${Date.now()}`;
      // zwei proposed gleichen Namens -> erlaubt
      await db!.insert(schema.merkmale).values([
        { slug: `${base}_p1`, name: `${base}_dup`, typ: "text", geltungsbereich: "both", status: "proposed" },
        { slug: `${base}_p2`, name: `${base}_dup`, typ: "text", geltungsbereich: "both", status: "proposed" },
      ]);
      // erstes active -> ok
      await db!.insert(schema.merkmale).values({
        slug: `${base}_a1`, name: `${base}_act`, typ: "text", geltungsbereich: "both", status: "active",
      });
      // zweites active gleichen Namens -> Verletzung
      await expect(
        db!.insert(schema.merkmale).values({
          slug: `${base}_a2`, name: `${base}_act`, typ: "text", geltungsbereich: "both", status: "active",
        }),
      ).rejects.toThrow();
    },
  );

  dbIt("EAV-Round-Trip: Brigade->Merkmal->Vehicle->value_num=2000", async () => {
    const stamp = Date.now();
    const [brigade] = await db!
      .insert(schema.brigades)
      .values({ name: `RT-Wehr-${stamp}` })
      .returning();
    const [merkmal] = await db!
      .insert(schema.merkmale)
      .values({
        slug: `rt_tank_${stamp}`,
        name: `RT Tankinhalt ${stamp}`,
        typ: "number",
        einheit: "l",
        geltungsbereich: "vehicle",
        status: "active",
      })
      .returning();
    const [vehicle] = await db!
      .insert(schema.vehicles)
      .values({ brigadeId: brigade!.id, name: `RT-TLFA-${stamp}`, funkrufname: "Florian RT 1" })
      .returning();
    await db!.insert(schema.merkmalValues).values({
      merkmalId: merkmal!.id,
      entityTyp: "vehicle",
      entityId: vehicle!.id,
      valueNum: 2000,
    });
    const res = await db!.execute(
      sql`SELECT value_num FROM merkmal_values WHERE entity_id = ${vehicle!.id}`,
    );
    expect(Number((res.rows[0] as { value_num: number }).value_num)).toBe(2000);
  });

  dbIt("FK-Verletzung wirft 23503", async () => {
    let code: string | undefined;
    try {
      await db!.insert(schema.merkmalValues).values({
        merkmalId: "00000000-0000-0000-0000-000000000000",
        entityTyp: "vehicle",
        entityId: "00000000-0000-0000-0000-000000000000",
        valueNum: 1,
      });
    } catch (e) {
      code = (e as { code?: string }).code;
    }
    expect(code).toBe("23503");
  });
});
