import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod/v3";
import { eq, getDb, authProviderConfig, encryptSecret } from "@groombook/db";
import { requireSuperUser } from "../middleware/rbac.js";
import { reinitAuth } from "../lib/auth.js";

export const authProviderRouter = new Hono();

const REDACTED = "••••••••";

const putAuthProviderSchema = z.object({
  providerId: z.string().min(1).max(100),
  displayName: z.string().min(1).max(200),
  issuerUrl: z.string().url(),
  internalBaseUrl: z.string().url().nullable().optional(),
  clientId: z.string().min(1),
  clientSecret: z.string().min(1),
  scopes: z.string().default("openid profile email"),
});

/** Minimal schema for the test endpoint — only issuer/internal URLs are needed for OIDC discovery. */
const authProviderTestSchema = z.object({
  issuerUrl: z.string().url(),
  internalBaseUrl: z.string().url().nullable().optional(),
});

/**
 * GET /api/admin/auth-provider
 * Returns the current provider config with clientSecret redacted.
 * Returns 404 if no provider is configured.
 */
authProviderRouter.get(
  "/",
  requireSuperUser(),
  async (c) => {
    const db = getDb();
    const [row] = await db
      .select()
      .from(authProviderConfig)
      .where(eq(authProviderConfig.enabled, true))
      .limit(1);

    if (!row) {
      return c.json({ error: "No auth provider configured" }, 404);
    }

    // Return with secret redacted
    return c.json({
      id: row.id,
      providerId: row.providerId,
      displayName: row.displayName,
      issuerUrl: row.issuerUrl,
      internalBaseUrl: row.internalBaseUrl,
      clientId: row.clientId,
      clientSecret: REDACTED,
      scopes: row.scopes,
      enabled: row.enabled,
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    });
  }
);

/**
 * PUT /api/admin/auth-provider
 * Creates or replaces the auth provider config.
 * The clientSecret is encrypted before storage.
 */
authProviderRouter.put(
  "/",
  requireSuperUser(),
  zValidator("json", putAuthProviderSchema),
  async (c) => {
    const db = getDb();
    const body = c.req.valid("json");

let encryptedSecret: string;
    try {
      encryptedSecret = encryptSecret(body.clientSecret);
    } catch (err) {
      const message = err instanceof Error ? err.message : "Unknown error";
      return c.json({ error: `Failed to encrypt client secret: ${message}` }, 500);
    }

    // Upsert: delete existing rows then insert atomically
    let row: typeof authProviderConfig.$inferSelect | undefined;
    try {
      [row] = await db.transaction(async (tx) => {
        await tx.delete(authProviderConfig);
        return tx.insert(authProviderConfig).values({
          providerId: body.providerId,
          displayName: body.displayName,
          issuerUrl: body.issuerUrl,
          internalBaseUrl: body.internalBaseUrl ?? null,
          clientId: body.clientId,
          clientSecret: encryptedSecret,
          scopes: body.scopes,
          enabled: true,
        }).returning();
      });
    } catch (err) {
      const message = err instanceof Error ? err.message : "Unknown error";
      return c.json({ error: `Failed to persist auth provider config: ${message}` }, 500);
    }

    if (!row) return c.json({ error: "Failed to create auth provider config" }, 500);

    try {
      await reinitAuth();
    } catch (err) {
      const message = err instanceof Error ? err.message : "Unknown error";
      return c.json({ error: `Failed to reinitialize auth: ${message}` }, 500);
    }

    return c.json({
      id: row.id,
      providerId: row.providerId,
      displayName: row.displayName,
      issuerUrl: row.issuerUrl,
      internalBaseUrl: row.internalBaseUrl,
      clientId: row.clientId,
      clientSecret: REDACTED,
      scopes: row.scopes,
      enabled: row.enabled,
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    });
  }
);

/**
 * POST /api/admin/auth-provider/test
 * Validates the provider config by hitting the OIDC discovery endpoint.
 * Returns {ok: true, metadata} on success or {ok: false, error: string} on failure.
 */
authProviderRouter.post(
  "/test",
  requireSuperUser(),
  zValidator("json", authProviderTestSchema),
  async (c) => {
    const body = c.req.valid("json");

    const discoveryUrl = `${body.issuerUrl.replace(/\/$/, "")}/.well-known/openid-configuration`;

    try {
      const res = await fetch(discoveryUrl, { signal: AbortSignal.timeout(10_000) });
      if (!res.ok) {
        return c.json({ ok: false, error: `Discovery endpoint returned ${res.status}` });
      }
      const metadata = await res.json() as Record<string, unknown>;
      return c.json({ ok: true, metadata });
    } catch (err) {
      const message = err instanceof Error ? err.message : "Unknown error";
      return c.json({ ok: false, error: message });
    }
  }
);

/**
 * DELETE /api/admin/auth-provider
 * Removes the auth provider config from the DB.
 * After this, auth falls back to OIDC_* env vars.
 */
authProviderRouter.delete(
  "/",
  requireSuperUser(),
  async (c) => {
    const db = getDb();
    await db.delete(authProviderConfig);
    try {
      await reinitAuth();
    } catch (err) {
      const message = err instanceof Error ? err.message : "Unknown error";
      return c.json({ error: `Failed to reinitialize auth: ${message}` }, 500);
    }
    return c.json({ ok: true });
  }
);
