"use server";

import { promises as fs } from "node:fs";
import path from "node:path";
import { headers } from "next/headers";
import { eq } from "drizzle-orm";
import { db, schema } from "@/lib/db";
import { auth } from "@/lib/auth";
import { buildSubmissionSchema, type FormField } from "@/lib/forms/types";
import { services, getDepositAmountEur } from "@/lib/services";
import { createClientAccount } from "@/lib/auth-account";
import { createCheckoutSession, isStripeConfigured } from "@/lib/stripe";
import { nextDossierReference, nextInvoiceNumber } from "@/lib/invoices";

type SubmitResult =
  | {
      ok: true;
      dossierReference: string;
      invoiceNumber: string;
      /** URL Stripe Checkout : si présente, le client doit être redirigé. */
      checkoutUrl?: string | null;
      /** Mode de paiement effectivement appliqué. */
      paymentMode: "card" | "bank" | "quote";
    }
  | {
      ok: false;
      fieldErrors?: Record<string, string>;
      globalError?: string;
    };

const UPLOADS_DIR =
  process.env.UPLOADS_DIR || path.join(process.cwd(), "uploads");

function safeFilename(name: string): string {
  const base = path.basename(name);
  return base.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 200);
}

export async function submitFormResponse(
  formData: FormData
): Promise<SubmitResult> {
  try {
    return await submitFormResponseInner(formData);
  } catch (err) {
    console.error("[submit-form] Erreur fatale :", err);
    return {
      ok: false,
      globalError:
        "Une erreur est survenue lors de la soumission. Veuillez réessayer ou nous contacter à contact@socialex.pro.",
    };
  }
}

async function submitFormResponseInner(
  formData: FormData
): Promise<SubmitResult> {
  const serviceSlug = formData.get("serviceSlug");
  if (typeof serviceSlug !== "string" || !serviceSlug) {
    return { ok: false, globalError: "Service introuvable." };
  }

  const service = services.find((s) => s.slug === serviceSlug);
  if (!service || service.published === false) {
    return { ok: false, globalError: "Service introuvable." };
  }

  const tpl = (
    await db
      .select()
      .from(schema.formTemplate)
      .where(eq(schema.formTemplate.serviceSlug, service.slug))
      .limit(1)
  )[0];

  if (!tpl || !tpl.published) {
    return {
      ok: false,
      globalError:
        "Le formulaire n'est pas disponible pour ce service. Contactez-nous par email.",
    };
  }

  const fields = tpl.fields as FormField[];
  const docRequests = (tpl.documentRequests as Array<{
    id: string;
    label: string;
    description?: string;
    required?: boolean;
    maxSizeMb?: number;
  }>) ?? [];

  /* ─── Parse FormData ─────────────────────────────────────────────── */
  const rawValues: Record<string, unknown> = {};
  const filesByField: Record<string, File[]> = {};
  const filesByDoc: Record<string, File[]> = {};
  const meta: Record<string, string> = {};

  for (const [key, val] of formData.entries()) {
    if (key.startsWith("value:")) {
      const fieldId = key.slice(6);
      const f = fields.find((x) => x.id === fieldId);
      if (!f) continue;
      if (f.type === "checkbox") {
        rawValues[fieldId] = val === "true";
      } else {
        rawValues[fieldId] = val;
      }
    } else if (key.startsWith("file:") && val instanceof File && val.size > 0) {
      filesByField[key.slice(5)] = (filesByField[key.slice(5)] ?? []).concat(val);
    } else if (key.startsWith("doc:") && val instanceof File && val.size > 0) {
      filesByDoc[key.slice(4)] = (filesByDoc[key.slice(4)] ?? []).concat(val);
    } else if (key.startsWith("meta:") && typeof val === "string") {
      meta[key.slice(5)] = val;
    }
  }

  /* ─── Validation Zod des champs business ─────────────────────────── */
  const submission = buildSubmissionSchema(fields).safeParse(rawValues);
  if (!submission.success) {
    const fieldErrors: Record<string, string> = {};
    for (const issue of submission.error.issues) {
      const key = issue.path[0];
      if (typeof key === "string" && !fieldErrors[key]) {
        fieldErrors[key] = issue.message;
      }
    }
    return {
      ok: false,
      fieldErrors,
      globalError: "Veuillez corriger les champs en erreur.",
    };
  }

  /* ─── Validation des fichiers ────────────────────────────────────── */
  const fileErrors: Record<string, string> = {};
  for (const f of fields) {
    if (f.type !== "file") continue;
    const files = filesByField[f.id] ?? [];
    if (f.required && files.length === 0) {
      fileErrors[f.id] = "Au moins un fichier est requis.";
      continue;
    }
    const maxBytes = (f.maxSizeMb ?? 25) * 1024 * 1024;
    const oversized = files.find((file) => file.size > maxBytes);
    if (oversized) {
      fileErrors[f.id] = `Le fichier ${oversized.name} dépasse ${f.maxSizeMb ?? 25} Mo.`;
    }
  }
  for (const d of docRequests) {
    const files = filesByDoc[d.id] ?? [];
    if (d.required && files.length === 0) {
      fileErrors[`doc:${d.id}`] = "Ce document est requis.";
      continue;
    }
    const maxBytes = (d.maxSizeMb ?? 25) * 1024 * 1024;
    const oversized = files.find((file) => file.size > maxBytes);
    if (oversized) {
      fileErrors[`doc:${d.id}`] = `Le fichier ${oversized.name} dépasse ${d.maxSizeMb ?? 25} Mo.`;
    }
  }
  if (Object.keys(fileErrors).length > 0) {
    return {
      ok: false,
      fieldErrors: fileErrors,
      globalError: "Veuillez corriger les fichiers en erreur.",
    };
  }

  /* ─── Validation des métadonnées (étape 3 : compte + paiement) ────── */
  const email = (meta.email ?? "").trim().toLowerCase();
  const firstName = (meta.firstName ?? "").trim();
  const lastName = (meta.lastName ?? "").trim();
  const phone = (meta.phone ?? "").trim();
  const password = meta.password ?? "";
  const paymentModeRaw = meta.paymentMode;
  const paymentMode: "card" | "bank" | "quote" =
    paymentModeRaw === "card"
      ? "card"
      : paymentModeRaw === "quote"
      ? "quote"
      : "bank";

  const fullName = `${firstName} ${lastName}`.trim() || "Nouveau client";

  const metaErrors: Record<string, string> = {};
  if (!firstName) metaErrors["meta:firstName"] = "Prénom requis.";
  if (!lastName) metaErrors["meta:lastName"] = "Nom requis.";
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email))
    metaErrors["meta:email"] = "Email invalide.";
  // Téléphone facultatif mais format si rempli.
  if (phone && !/^[\d\s+()-]{6,}$/.test(phone))
    metaErrors["meta:phone"] = "Numéro de téléphone invalide.";

  // Pour les modes "card" et "bank" : password obligatoire (création de compte).
  // Pour "quote" : pas de paiement → on accepte pas de password.
  if (paymentMode !== "quote" && password.length < 10)
    metaErrors["meta:password"] =
      "Mot de passe trop court (10 caractères minimum).";

  if (Object.keys(metaErrors).length > 0) {
    return {
      ok: false,
      fieldErrors: metaErrors,
      globalError: "Veuillez compléter vos informations.",
    };
  }

  /* ─── Création / récupération du compte client ────────────────────── */
  let userId: string;
  let isNewAccount = false;

  if (paymentMode === "quote") {
    // Mode devis : pas de mot de passe demandé. On crée juste un user
    // sans account credential (pas de connexion possible immédiate ; il
    // recevra un lien `/forgot-password` pour activer son compte).
    const existing = await db
      .select()
      .from(schema.user)
      .where(eq(schema.user.email, email))
      .limit(1);
    if (existing.length > 0) {
      userId = existing[0].id;
    } else {
      userId = crypto.randomUUID();
      await db.insert(schema.user).values({
        id: userId,
        email,
        name: fullName,
        emailVerified: false,
        role: "client",
      });
      isNewAccount = true;
    }
  } else {
    const accountResult = await createClientAccount({
      email,
      password,
      name: fullName,
    });
    if (!accountResult.ok) {
      return { ok: false, globalError: accountResult.error };
    }
    userId = accountResult.userId;
    isNewAccount = accountResult.isNew;
  }

  /* ─── Connexion auto (sauf mode quote) ────────────────────────────── */
  if (paymentMode !== "quote" && isNewAccount) {
    try {
      await auth.api.signInEmail({
        body: { email, password },
        headers: await headers(),
      });
    } catch (err) {
      console.error("[submit-form] Auto sign-in failed:", err);
      // Non bloquant : on continue, le client recevra un email pour se connecter.
    }
  }

  // Profil client (téléphone)
  if (phone) {
    const existingProfile = (
      await db
        .select()
        .from(schema.clientProfile)
        .where(eq(schema.clientProfile.userId, userId))
        .limit(1)
    )[0];
    if (existingProfile) {
      await db
        .update(schema.clientProfile)
        .set({ phone, updatedAt: new Date() })
        .where(eq(schema.clientProfile.userId, userId));
    } else {
      await db.insert(schema.clientProfile).values({
        userId,
        phone,
      });
    }
  }

  /* ─── Création dossier ────────────────────────────────────────────── */
  const year = new Date().getFullYear();
  const dossierRef = await nextDossierReference(year);

  const data = submission.data as Record<string, unknown>;
  const formDataForStorage: Record<string, unknown> = {
    ...data,
    _contact: { firstName, lastName, email, phone },
  };
  for (const f of fields) {
    if (f.type === "file") {
      const files = filesByField[f.id] ?? [];
      if (files.length > 0) {
        formDataForStorage[f.id] = files.map((file) => file.name);
      }
    }
  }

  // Status initial selon le mode
  // - card : status `formulaire` jusqu'au webhook Stripe (→ acompte_paye)
  // - bank : status `en_attente_paiement` (admin valide manuellement)
  // - quote : status `formulaire` (pas de paiement, contact manuel)
  const initialStatus: "formulaire" | "en_attente_paiement" =
    paymentMode === "bank" ? "en_attente_paiement" : "formulaire";

  const [dossier] = await db
    .insert(schema.dossier)
    .values({
      reference: dossierRef,
      clientId: userId,
      serviceSlug: service.slug,
      title: tpl.title,
      status: initialStatus,
      formData: formDataForStorage,
    })
    .returning();

  /* ─── Écriture des fichiers + rows document ──────────────────────── */
  const now = new Date();

  for (const f of fields) {
    if (f.type !== "file") continue;
    const files = filesByField[f.id] ?? [];
    if (files.length === 0) continue;
    const fieldDir = path.join(UPLOADS_DIR, dossier.id, f.id);
    await fs.mkdir(fieldDir, { recursive: true });
    for (const file of files) {
      const safeName = safeFilename(file.name);
      const finalName = `${crypto.randomUUID()}-${safeName}`;
      const absPath = path.join(fieldDir, finalName);
      const bytes = Buffer.from(await file.arrayBuffer());
      await fs.writeFile(absPath, bytes);
      await db.insert(schema.document).values({
        dossierId: dossier.id,
        label: f.label,
        description: `Joint au formulaire (champ : ${f.id})`,
        status: "uploade",
        fileName: file.name,
        filePath: path.join(dossier.id, f.id, finalName),
        fileSizeBytes: file.size,
        mimeType: file.type || null,
        uploadedById: userId,
        uploadedAt: now,
      });
    }
  }

  for (const d of docRequests) {
    const files = filesByDoc[d.id] ?? [];
    if (files.length === 0) {
      await db.insert(schema.document).values({
        dossierId: dossier.id,
        label: d.label,
        description: d.description ?? null,
        status: "demande",
      });
      continue;
    }
    const docDir = path.join(UPLOADS_DIR, dossier.id, `doc-${d.id}`);
    await fs.mkdir(docDir, { recursive: true });
    for (const file of files) {
      const safeName = safeFilename(file.name);
      const finalName = `${crypto.randomUUID()}-${safeName}`;
      const absPath = path.join(docDir, finalName);
      const bytes = Buffer.from(await file.arrayBuffer());
      await fs.writeFile(absPath, bytes);
      await db.insert(schema.document).values({
        dossierId: dossier.id,
        label: d.label,
        description: d.description ?? null,
        status: "uploade",
        fileName: file.name,
        filePath: path.join(dossier.id, `doc-${d.id}`, finalName),
        fileSizeBytes: file.size,
        mimeType: file.type || null,
        uploadedById: userId,
        uploadedAt: now,
      });
    }
  }

  /* ─── Création factures (acompte + solde) ────────────────────────── */
  const depositEur = getDepositAmountEur(service);
  let acompteInvoiceId: string | null = null;
  let acompteInvoiceNumber: string = "F-PENDING";
  let totalEur: number | null = null;

  if (paymentMode !== "quote" && depositEur != null) {
    // On utilise le `priceEur` du service comme total de référence.
    // Pour les services à plusieurs forfaits, c'est le minimum (l'admin
    // ajustera si une variante plus chère est confirmée).
    totalEur = service.priceEur ?? depositEur * 2;
    const soldeEur = totalEur - depositEur;

    acompteInvoiceNumber = await nextInvoiceNumber(year);
    // Suffixe acompte +1 pour le solde (l'acompte n'est pas encore inséré,
    // donc deux appels successifs renverraient le même numéro).
    const acompteMatch = acompteInvoiceNumber.match(/^F-\d{4}-(\d+)$/);
    const acompteSeq = acompteMatch ? parseInt(acompteMatch[1], 10) : 1;
    const soldeInvoiceNumber = `F-${year}-${String(acompteSeq + 1).padStart(4, "0")}`;

    const dueAcompte = new Date();
    dueAcompte.setDate(dueAcompte.getDate() + 7);
    const dueSolde = new Date();
    dueSolde.setDate(dueSolde.getDate() + 60);

    // Acompte
    const [acompteInvoice] = await db
      .insert(schema.invoice)
      .values({
        number: acompteInvoiceNumber,
        clientId: userId,
        dossierId: dossier.id,
        status: "envoyee",
        amountCents: depositEur * 100,
        description: `Acompte 50 % — ${tpl.title}`,
        issuedAt: now,
        dueAt: dueAcompte,
        paymentMethod: paymentMode === "card" ? "CB" : "virement",
      })
      .returning();
    acompteInvoiceId = acompteInvoice.id;

    // Solde
    if (soldeEur > 0) {
      await db.insert(schema.invoice).values({
        number: soldeInvoiceNumber,
        clientId: userId,
        dossierId: dossier.id,
        status: "envoyee",
        amountCents: soldeEur * 100,
        description: `Solde 50 % — ${tpl.title} (à régler à l'issue de la prestation)`,
        issuedAt: now,
        dueAt: dueSolde,
      });
    }
  }

  /* ─── Activity log ────────────────────────────────────────────────── */
  await db.insert(schema.activityLog).values({
    dossierId: dossier.id,
    actorId: userId,
    action: "dossier_cree_par_formulaire",
    payload: {
      paymentMode,
      acompteInvoiceNumber,
      depositEur,
      totalEur,
      isNewAccount,
    },
  });

  /* ─── Stripe Checkout (si mode card) ──────────────────────────────── */
  let checkoutUrl: string | null = null;

  if (paymentMode === "card" && acompteInvoiceId && depositEur != null) {
    if (!isStripeConfigured()) {
      // Stripe pas configuré : on bascule sur le mode virement
      await db
        .update(schema.dossier)
        .set({ status: "en_attente_paiement", updatedAt: now })
        .where(eq(schema.dossier.id, dossier.id));
    } else {
      const baseUrl =
        process.env.BETTER_AUTH_URL ?? "http://localhost:3001";
      const session = await createCheckoutSession({
        dossierReference: dossierRef,
        dossierId: dossier.id,
        invoiceId: acompteInvoiceId,
        invoiceNumber: acompteInvoiceNumber,
        label: `Acompte 50 % — ${tpl.title}`,
        amountCents: depositEur * 100,
        customerEmail: email,
        successUrl: `${baseUrl}/societes/merci?dossier=${dossierRef}&session_id={CHECKOUT_SESSION_ID}`,
        cancelUrl: `${baseUrl}/societes/merci?dossier=${dossierRef}&canceled=1`,
      });
      checkoutUrl = session.url;
      // On stocke l'ID de session sur la facture pour le webhook
      if (session.sessionId) {
        await db
          .update(schema.invoice)
          .set({ paymentReference: session.sessionId })
          .where(eq(schema.invoice.id, acompteInvoiceId));
      }
    }
  }

  /* ─── Email confirmation (sauf mode card → Stripe envoie son propre email) */
  if (paymentMode !== "card") {
    try {
      const { sendInvoiceEmail } = await import("@/lib/email");
      const { createPasswordResetToken } = await import("@/lib/auth-tokens");
      const baseUrl =
        process.env.BETTER_AUTH_URL ?? "http://localhost:3001";

      // Pour mode quote : magic link pour activer le compte (pas de password)
      // Pour mode bank : magic link aussi (au cas où le client veut se reconnecter)
      const token = await createPasswordResetToken(userId);
      const setPasswordLink =
        paymentMode === "quote"
          ? `${baseUrl}/set-password?token=${token}&uid=${userId}`
          : `${baseUrl}/login`;

      await sendInvoiceEmail({
        to: email,
        clientName: fullName,
        dossierReference: dossierRef,
        invoiceNumber: acompteInvoiceNumber,
        amountCents: (depositEur ?? 0) * 100,
        serviceTitle: tpl.title,
        setPasswordLink,
        paymentMode,
      });
    } catch (err) {
      console.error("[submit-form] Échec envoi email :", err);
    }
  }

  return {
    ok: true,
    dossierReference: dossierRef,
    invoiceNumber: acompteInvoiceNumber,
    checkoutUrl,
    paymentMode,
  };
}
