// Tests für die seedAdmin-Convenience aus auth-email-password/testing.
//
// Wert: seedAdmin orchestriert seedTenant × N + seedUserWithPassword +
// seedTenantMembership × N. Die Einzel-Helper haben jeweils eigene Tests
// (tenant/seed-testing, user/seed-testing). Hier prüfen wir nur was
// SEEDADMIN selber zusagt:
//   1. Reihenfolge stimmt — Tenants vor User vor Memberships.
//   2. Password wird mit argon2 gehasht und ist via verifyPassword(plain, hash) gültig.
//   3. Re-Run ist idempotent (für persistent-DB-Modus im dev-server).
//   4. Rollen pro Tenant landen korrekt (unterschiedliche Rollen-Listen
//      pro Membership).

import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
import {
  setupTestStack,
  type TestStack,
  unsafeCreateEntityTable,
  unsafePushTables,
} from "@cosmicdrift/kumiko-framework/stack";
import { createConfigFeature } from "../../config/feature";
import { createConfigResolver } from "../../config/resolver";
import { configValuesTable } from "../../config/table";
import { createTenantFeature } from "../../tenant/feature";
import { tenantMembershipsTable } from "../../tenant/membership-table";
import { tenantEntity, tenantTable } from "../../tenant/schema/tenant";
import { createUserFeature } from "../../user/feature";
import { userEntity, userTable } from "../../user/schema/user";
import { verifyPassword } from "../password-hashing";
import { seedAdmin } from "../testing";

let stack: TestStack;

const TENANT_DEV: TenantId = "00000000-0000-4000-8000-000000000d11" as TenantId;
const TENANT_BETA: TenantId = "00000000-0000-4000-8000-000000000be1" as TenantId;

beforeAll(async () => {
  const resolver = createConfigResolver();
  stack = await setupTestStack({
    features: [createConfigFeature(), createUserFeature(), createTenantFeature()],
    extraContext: { configResolver: resolver },
  });
  await unsafeCreateEntityTable(stack.db, tenantEntity);
  await unsafeCreateEntityTable(stack.db, userEntity);
  await unsafePushTables(stack.db, { configValuesTable, tenantMembershipsTable });
  await createEventsTable(stack.db);
});

afterAll(async () => {
  await stack.cleanup();
});

beforeEach(async () => {
  await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
  await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantTable.tableName}"`);
  await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
  await asRawClient(stack.db).unsafe(`DELETE FROM "${eventsTable.tableName}"`);
});

describe("seedAdmin", () => {
  test("legt Tenants, User mit gehashtem Password und Memberships an — Login-Roundtrip funktioniert", async () => {
    const { id: userId } = await seedAdmin(stack.db, {
      email: "admin@example.com",
      password: "secret-pw",
      displayName: "Admin",
      memberships: [
        {
          tenantId: TENANT_DEV,
          tenantKey: "dev",
          tenantName: "Dev",
          roles: ["Admin"],
        },
        {
          tenantId: TENANT_BETA,
          tenantKey: "beta",
          tenantName: "Beta",
          roles: ["User"],
        },
      ],
    });

    // Tenants angelegt
    const tenants = await selectMany(stack.db, tenantTable);
    expect(tenants.map((t) => t["id"]).sort()).toEqual([TENANT_DEV, TENANT_BETA].sort());

    // User angelegt mit Hash (NICHT plain-Password)
    const [user] = await selectMany(stack.db, userTable, { email: "admin@example.com" });
    expect(user?.["id"]).toBe(userId);
    expect(user?.["passwordHash"]).not.toBe("secret-pw");
    expect(user?.["passwordHash"]).toMatch(/^\$argon2/);

    // verifyPassword(hash, plain) — Login-Pfad würde durchgehen.
    const valid = await verifyPassword(user?.["passwordHash"] as string, "secret-pw");
    expect(valid).toBe(true);
    const invalid = await verifyPassword(user?.["passwordHash"] as string, "wrong-pw");
    expect(invalid).toBe(false);

    // Memberships pro Tenant mit unterschiedlichen Rollen
    const devMembership = await selectMany(stack.db, tenantMembershipsTable, {
      userId: userId,
      tenantId: TENANT_DEV,
    });
    expect(devMembership[0]?.["roles"]).toBe(JSON.stringify(["Admin"]));

    const betaMembership = await selectMany(stack.db, tenantMembershipsTable, {
      userId: userId,
      tenantId: TENANT_BETA,
    });
    expect(betaMembership[0]?.["roles"]).toBe(JSON.stringify(["User"]));
  });

  test("idempotent: zweiter Aufruf no-op (kein Crash, Stand bleibt)", async () => {
    // Erstaufruf
    const { id: userId1 } = await seedAdmin(stack.db, {
      email: "admin@example.com",
      password: "pw1",
      displayName: "Admin",
      memberships: [
        { tenantId: TENANT_DEV, tenantKey: "dev", tenantName: "Dev", roles: ["Admin"] },
      ],
    });
    // Zweiter Aufruf — gleicher Email, anderes Password (würde theoretisch
    // einen neuen Hash erzeugen und neu schreiben, der idempotent-Check
    // greift VOR dem Insert).
    const { id: userId2 } = await seedAdmin(stack.db, {
      email: "admin@example.com",
      password: "pw2",
      displayName: "Admin",
      memberships: [
        { tenantId: TENANT_DEV, tenantKey: "dev", tenantName: "Dev", roles: ["Admin"] },
      ],
    });
    expect(userId2).toBe(userId1);

    // Genau ein User-Row, original-Hash (passt zu pw1, nicht pw2).
    const users = await selectMany(stack.db, userTable);
    expect(users).toHaveLength(1);
    const valid = await verifyPassword(users[0]?.["passwordHash"] as string, "pw1");
    expect(valid).toBe(true);
    const invalid = await verifyPassword(users[0]?.["passwordHash"] as string, "pw2");
    expect(invalid).toBe(false);

    // Genau ein Membership-Row.
    const memberships = await selectMany(stack.db, tenantMembershipsTable);
    expect(memberships).toHaveLength(1);

    // Genau ein .created-Event pro Aggregat-Typ.
    const events = await selectMany(stack.db, eventsTable);
    const createdByType = events
      .filter((e) => e.type.endsWith(".created"))
      .reduce<Record<string, number>>((acc, e) => {
        acc[e.aggregateType] = (acc[e.aggregateType] ?? 0) + 1;
        return acc;
      }, {});
    expect(createdByType).toEqual({
      tenant: 1,
      user: 1,
      "tenant-membership": 1,
    });
  });
});
