"use server";

import { z } from "zod";
import { eq, count, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { db, schema } from "@/lib/db";
import { requireAdmin } from "@/lib/guards";
import { auth } from "@/lib/auth";

const updateInput = z.object({
  userId: z.string().min(1),
  name: z.string().min(2, "Nom trop court").max(120),
  email: z.string().email("Email invalide"),
  company: z.string().max(120).optional().nullable(),
  siret: z.string().max(14).optional().nullable(),
  phone: z.string().max(40).optional().nullable(),
  addressStreet: z.string().max(200).optional().nullable(),
  addressZip: z.string().max(20).optional().nullable(),
  addressCity: z.string().max(80).optional().nullable(),
  addressCountry: z.string().max(2).optional().nullable(),
  notes: z.string().max(2000).optional().nullable(),
});

export type UpdateClientInput = z.infer<typeof updateInput>;

export async function updateClient(
  input: UpdateClientInput
): Promise<{ ok: true } | { ok: false; error: string }> {
  await requireAdmin();
  const parsed = updateInput.safeParse(input);
  if (!parsed.success) {
    return { ok: false, error: parsed.error.issues[0]?.message ?? "Données invalides" };
  }
  const d = parsed.data;

  // Vérif unicité email (si changé)
  const current = (
    await db
      .select()
      .from(schema.user)
      .where(eq(schema.user.id, d.userId))
      .limit(1)
  )[0];
  if (!current) return { ok: false, error: "Utilisateur introuvable" };

  if (d.email !== current.email) {
    const dup = await db
      .select({ id: schema.user.id })
      .from(schema.user)
      .where(eq(schema.user.email, d.email))
      .limit(1);
    if (dup.length > 0) {
      return { ok: false, error: "Cet email est déjà utilisé par un autre compte" };
    }
  }

  await db
    .update(schema.user)
    .set({ name: d.name, email: d.email, updatedAt: new Date() })
    .where(eq(schema.user.id, d.userId));

  // Upsert clientProfile
  const existingProfile = (
    await db
      .select()
      .from(schema.clientProfile)
      .where(eq(schema.clientProfile.userId, d.userId))
      .limit(1)
  )[0];

  const profileData = {
    company: d.company ?? null,
    siret: d.siret ?? null,
    phone: d.phone ?? null,
    addressStreet: d.addressStreet ?? null,
    addressZip: d.addressZip ?? null,
    addressCity: d.addressCity ?? null,
    addressCountry: d.addressCountry ?? "FR",
    notes: d.notes ?? null,
    updatedAt: new Date(),
  };

  if (existingProfile) {
    await db
      .update(schema.clientProfile)
      .set(profileData)
      .where(eq(schema.clientProfile.userId, d.userId));
  } else {
    await db.insert(schema.clientProfile).values({
      userId: d.userId,
      ...profileData,
    });
  }

  revalidatePath("/socialexadmin/clients");
  revalidatePath(`/socialexadmin/clients/${d.userId}`);
  return { ok: true };
}

const passwordInput = z.object({
  userId: z.string().min(1),
  newPassword: z.string().min(10, "Mot de passe trop court (10 caractères min)"),
});

export async function updateClientPassword(
  input: z.infer<typeof passwordInput>
): Promise<{ ok: true } | { ok: false; error: string }> {
  await requireAdmin();
  const parsed = passwordInput.safeParse(input);
  if (!parsed.success) {
    return { ok: false, error: parsed.error.issues[0]?.message ?? "Données invalides" };
  }
  const { userId, newPassword } = parsed.data;

  // Vérifie que l'utilisateur existe
  const u = (
    await db
      .select()
      .from(schema.user)
      .where(eq(schema.user.id, userId))
      .limit(1)
  )[0];
  if (!u) return { ok: false, error: "Utilisateur introuvable" };

  try {
    // Better Auth expose le hash via le context interne pour les opérations admin.
    const ctx = await auth.$context;
    const hashed = await ctx.password.hash(newPassword);

    // Récupère ou crée la row account "credential" pour ce user.
    const accountRow = (
      await db
        .select()
        .from(schema.account)
        .where(eq(schema.account.userId, userId))
        .limit(1)
    )[0];

    if (accountRow) {
      await db
        .update(schema.account)
        .set({ password: hashed, updatedAt: new Date() })
        .where(eq(schema.account.id, accountRow.id));
    } else {
      // Cas : user créé via le formulaire public sans password initial.
      await db.insert(schema.account).values({
        id: crypto.randomUUID(),
        userId,
        accountId: u.email,
        providerId: "credential",
        password: hashed,
      });
    }

    // Invalide les sessions en cours pour forcer une re-connexion.
    await db
      .delete(schema.session)
      .where(eq(schema.session.userId, userId));

    revalidatePath(`/socialexadmin/clients/${userId}`);
    return { ok: true };
  } catch (err) {
    console.error("[updateClientPassword] failed:", err);
    return { ok: false, error: "Échec de la mise à jour du mot de passe" };
  }
}

export async function deleteClient(userId: string): Promise<void> {
  await requireAdmin();

  // FK `restrict` sur dossier ET invoice : on doit pré-vérifier les deux,
  // sinon Postgres remonte une violation FK qui se transforme en erreur
  // serveur opaque dans le build prod.
  const dossierCount = await db
    .select({ c: sql<number>`count(*)::int` })
    .from(schema.dossier)
    .where(eq(schema.dossier.clientId, userId));

  if ((dossierCount[0]?.c ?? 0) > 0) {
    throw new Error(
      "Impossible de supprimer ce client : il a des dossiers associés. Annulez ou archivez ses dossiers d'abord."
    );
  }

  const invoiceCount = await db
    .select({ c: sql<number>`count(*)::int` })
    .from(schema.invoice)
    .where(eq(schema.invoice.clientId, userId));

  if ((invoiceCount[0]?.c ?? 0) > 0) {
    throw new Error(
      "Impossible de supprimer ce client : il a des factures associées. Annulez-les d'abord (ou conservez-les pour les obligations comptables)."
    );
  }

  // Cascade automatique sur session, account, verification, clientProfile via FK.
  // Si l'admin a une activité log avec ce user comme actor, on dénoue d'abord
  // (FK actor_id sans cascade explicite).
  await db
    .update(schema.activityLog)
    .set({ actorId: null })
    .where(eq(schema.activityLog.actorId, userId));

  // Idem pour document.uploadedById / requestedById / validatedById.
  await db
    .update(schema.document)
    .set({ uploadedById: null })
    .where(eq(schema.document.uploadedById, userId));
  await db
    .update(schema.document)
    .set({ requestedById: null })
    .where(eq(schema.document.requestedById, userId));
  await db
    .update(schema.document)
    .set({ validatedById: null })
    .where(eq(schema.document.validatedById, userId));

  try {
    await db.delete(schema.user).where(eq(schema.user.id, userId));
  } catch (err) {
    console.error("[deleteClient] DB error:", err);
    throw new Error(
      "Suppression impossible : des références internes empêchent l'effacement. Contactez le support technique."
    );
  }

  revalidatePath("/socialexadmin/clients");
  redirect("/socialexadmin/clients");
}
