"use server";

// Server actions pour la gestion des clients cote admin.
// Toutes les actions verifient `requireAdmin()` avant tout effet de bord.

import { randomBytes, randomUUID } from "node:crypto";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { eq } from "drizzle-orm";
import { hashPassword } from "better-auth/crypto";
import { db } from "@/lib/db";
import { user, account, clientAccount, clientSite } from "@/lib/db/schema";
import { requireAdmin } from "@/lib/auth/server";
import {
  getSearchConsoleClient,
  getAnalyticsDataClient,
  hasGoogleCredentials,
} from "@/lib/google";
import { sendMail } from "@/lib/email/send";
import {
  welcomeClientSubject,
  welcomeClientText,
  welcomeClientHtml,
} from "@/lib/email/templates";

type Plan = "presence" | "developpement" | "ecommerce";

const PLAN_DEFAULT_AMOUNT_CENTS: Record<Plan, number> = {
  presence: 4999, // 49,99 €
  developpement: 9900, // 99 €
  ecommerce: 19900, // 199 €
};

export type CreateClientResult =
  | { ok: true; clientAccountId: string; userEmail: string; generatedPassword: string }
  | { ok: false; error: string };

function generateStrongPassword(): string {
  // 18 octets en base64url ≈ 24 caracteres, fortement aleatoires, URL-safe.
  return randomBytes(18).toString("base64url");
}

export async function createClient(formData: FormData): Promise<CreateClientResult> {
  await requireAdmin();

  const companyName = String(formData.get("companyName") ?? "").trim();
  const contactName = String(formData.get("contactName") ?? "").trim();
  const contactEmail = String(formData.get("contactEmail") ?? "").trim().toLowerCase();
  const domain = String(formData.get("domain") ?? "").trim().toLowerCase();
  const plan = String(formData.get("plan") ?? "presence") as Plan;

  if (!companyName) return { ok: false, error: "La raison sociale est obligatoire." };
  if (!contactName) return { ok: false, error: "Le nom du contact est obligatoire." };
  if (!contactEmail) return { ok: false, error: "L'email du contact est obligatoire." };
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(contactEmail)) {
    return { ok: false, error: "L'email du contact est invalide." };
  }
  if (!(plan in PLAN_DEFAULT_AMOUNT_CENTS)) {
    return { ok: false, error: "Plan invalide." };
  }

  // Verifier que l'email n'est pas deja utilise par un compte Better Auth.
  const existingUser = await db
    .select({ id: user.id })
    .from(user)
    .where(eq(user.email, contactEmail))
    .limit(1);
  if (existingUser.length > 0) {
    return { ok: false, error: "Un compte avec cet email existe deja." };
  }

  // Verifier qu'on ne provisionne pas deux fois le meme domaine (si renseigne).
  if (domain) {
    const existingSite = await db
      .select({ id: clientSite.id })
      .from(clientSite)
      .where(eq(clientSite.domain, domain))
      .limit(1);
    if (existingSite.length > 0) {
      return { ok: false, error: "Ce domaine est deja rattache a un autre compte." };
    }
  }

  const clientAccountId = randomUUID();
  const userId = randomUUID();
  const generatedPassword = generateStrongPassword();
  const hashed = await hashPassword(generatedPassword);

  try {
    await db.transaction(async (tx) => {
      await tx.insert(clientAccount).values({
        id: clientAccountId,
        companyName,
        contactName,
        contactEmail,
        plan,
        status: "actif",
      });

      if (domain) {
        await tx.insert(clientSite).values({
          id: randomUUID(),
          clientAccountId,
          domain,
          label: companyName,
          status: "en_preparation",
          plan,
          monthlyAmountCents: PLAN_DEFAULT_AMOUNT_CENTS[plan],
        });
      }

      await tx.insert(user).values({
        id: userId,
        email: contactEmail,
        name: contactName,
        emailVerified: false,
        role: "user",
        banned: false,
        clientAccountId,
      });

      await tx.insert(account).values({
        id: randomBytes(16).toString("hex"),
        userId,
        accountId: userId,
        providerId: "credential",
        password: hashed,
      });
    });
  } catch (err) {
    console.error("[createClient] tx failed", err);
    return { ok: false, error: "Erreur lors de la creation. Reessayez." };
  }

  // Welcome email avec credentials. Non bloquant : si l'envoi rate, le compte
  // est cree quand meme et l'admin voit le mot de passe dans l'UI pour le
  // partager manuellement.
  const baseUrl =
    process.env.NEXT_PUBLIC_SITE_URL ?? process.env.BETTER_AUTH_URL ?? "";
  const loginUrl = baseUrl
    ? `${baseUrl.replace(/\/$/, "")}/connexion`
    : "/connexion";
  const welcomeResult = await sendMail({
    to: contactEmail,
    subject: welcomeClientSubject({
      contactName,
      companyName,
      email: contactEmail,
      temporaryPassword: generatedPassword,
      loginUrl,
    }),
    text: welcomeClientText({
      contactName,
      companyName,
      email: contactEmail,
      temporaryPassword: generatedPassword,
      loginUrl,
    }),
    html: welcomeClientHtml({
      contactName,
      companyName,
      email: contactEmail,
      temporaryPassword: generatedPassword,
      loginUrl,
    }),
  });
  if (!welcomeResult.ok) {
    console.warn("[createClient] welcome email failed:", welcomeResult.error);
    // Pas bloquant : on continue, l'admin a le password dans la response UI.
  }

  revalidatePath("/atelier-novelia/clients");
  return {
    ok: true,
    clientAccountId,
    userEmail: contactEmail,
    generatedPassword,
  };
}

export async function redirectToClientsList() {
  await requireAdmin();
  redirect("/atelier-novelia/clients");
}

// === Sites ===

export type SiteActionResult = { ok: true } | { ok: false; error: string };

function normalizeDomain(input: string): string {
  return input
    .trim()
    .toLowerCase()
    .replace(/^https?:\/\//, "")
    .replace(/\/$/, "");
}

// Normalise l'identifiant Search Console.
// Search Console connait 2 types de proprietes :
// - Domain property : `sc-domain:example.com` (couvre tous protocoles et sous-domaines)
// - URL prefix property : `https://example.com/` (URL specifique avec slash final)
// L'utilisateur peut taper l'un des trois formats :
//   - "example.com"           -> "sc-domain:example.com"   (defaut, propriete domain)
//   - "sc-domain:example.com" -> tel quel
//   - "https://example.com/"  -> tel quel (slash ajoute si manquant)
function normalizeSearchConsoleSiteUrl(input: string): string | null {
  const v = input.trim();
  if (!v) return null;
  if (v.startsWith("sc-domain:")) return v.toLowerCase();
  if (v.startsWith("https://") || v.startsWith("http://")) {
    return v.endsWith("/") ? v : v + "/";
  }
  return "sc-domain:" + v.toLowerCase();
}

export async function addSite(
  clientAccountIdInput: string,
  formData: FormData,
): Promise<SiteActionResult> {
  await requireAdmin();

  const domain = normalizeDomain(String(formData.get("domain") ?? ""));
  const label = String(formData.get("label") ?? "").trim() || null;
  const plan = String(formData.get("plan") ?? "presence") as Plan;
  const status = String(formData.get("status") ?? "en_preparation");
  const searchConsoleSiteUrl = normalizeSearchConsoleSiteUrl(
    String(formData.get("searchConsoleSiteUrl") ?? ""),
  );
  const ga4PropertyId = String(formData.get("ga4PropertyId") ?? "").trim() || null;

  if (!domain) return { ok: false, error: "Le domaine est obligatoire." };
  if (!(plan in PLAN_DEFAULT_AMOUNT_CENTS)) return { ok: false, error: "Plan invalide." };
  if (!["en_preparation", "en_ligne", "maintenance"].includes(status)) {
    return { ok: false, error: "Statut invalide." };
  }

  // Verifier que le compte existe.
  const parent = await db
    .select({ id: clientAccount.id })
    .from(clientAccount)
    .where(eq(clientAccount.id, clientAccountIdInput))
    .limit(1);
  if (parent.length === 0) {
    return { ok: false, error: "Compte client introuvable." };
  }

  // Verifier l'unicite du domaine.
  const existing = await db
    .select({ id: clientSite.id })
    .from(clientSite)
    .where(eq(clientSite.domain, domain))
    .limit(1);
  if (existing.length > 0) {
    return { ok: false, error: "Ce domaine est deja rattache a un autre compte." };
  }

  await db.insert(clientSite).values({
    id: randomUUID(),
    clientAccountId: clientAccountIdInput,
    domain,
    label,
    status,
    plan,
    monthlyAmountCents: PLAN_DEFAULT_AMOUNT_CENTS[plan],
    searchConsoleSiteUrl,
    ga4PropertyId,
  });

  revalidatePath(`/atelier-novelia/clients/${clientAccountIdInput}`);
  return { ok: true };
}

export async function updateSite(
  siteId: string,
  formData: FormData,
): Promise<SiteActionResult> {
  await requireAdmin();

  const domain = normalizeDomain(String(formData.get("domain") ?? ""));
  const label = String(formData.get("label") ?? "").trim() || null;
  const plan = String(formData.get("plan") ?? "presence") as Plan;
  const status = String(formData.get("status") ?? "en_preparation");
  const searchConsoleSiteUrl = normalizeSearchConsoleSiteUrl(
    String(formData.get("searchConsoleSiteUrl") ?? ""),
  );
  const ga4PropertyId = String(formData.get("ga4PropertyId") ?? "").trim() || null;

  if (!domain) return { ok: false, error: "Le domaine est obligatoire." };
  if (!(plan in PLAN_DEFAULT_AMOUNT_CENTS)) return { ok: false, error: "Plan invalide." };
  if (!["en_preparation", "en_ligne", "maintenance"].includes(status)) {
    return { ok: false, error: "Statut invalide." };
  }

  const existing = await db
    .select()
    .from(clientSite)
    .where(eq(clientSite.id, siteId))
    .limit(1);
  if (existing.length === 0) {
    return { ok: false, error: "Site introuvable." };
  }
  const current = existing[0];

  // Si le domaine a change, verifier l'unicite.
  if (domain !== current.domain) {
    const conflict = await db
      .select({ id: clientSite.id })
      .from(clientSite)
      .where(eq(clientSite.domain, domain))
      .limit(1);
    if (conflict.length > 0) {
      return { ok: false, error: "Ce domaine est deja rattache a un autre site." };
    }
  }

  await db
    .update(clientSite)
    .set({
      domain,
      label,
      plan,
      status,
      monthlyAmountCents: PLAN_DEFAULT_AMOUNT_CENTS[plan],
      searchConsoleSiteUrl,
      ga4PropertyId,
      updatedAt: new Date(),
    })
    .where(eq(clientSite.id, siteId));

  revalidatePath(`/atelier-novelia/clients/${current.clientAccountId}`);
  return { ok: true };
}

// === Test de connexion aux APIs Google ===

export type GoogleCheck = "ok" | "missing_config" | "no_credentials" | string;

export type TestConnectionResult = {
  searchConsole: GoogleCheck;
  analytics: GoogleCheck;
};

export async function testSiteConnection(siteId: string): Promise<TestConnectionResult> {
  await requireAdmin();

  const rows = await db
    .select()
    .from(clientSite)
    .where(eq(clientSite.id, siteId))
    .limit(1);

  if (rows.length === 0) {
    return {
      searchConsole: "Site introuvable.",
      analytics: "Site introuvable.",
    };
  }
  const site = rows[0];

  if (!(await hasGoogleCredentials())) {
    return {
      searchConsole: "no_credentials",
      analytics: "no_credentials",
    };
  }

  // === Search Console ===
  let gsc: GoogleCheck;
  if (!site.searchConsoleSiteUrl) {
    gsc = "missing_config";
  } else {
    try {
      const sc = await getSearchConsoleClient();
      if (!sc) {
        gsc = "no_credentials";
      } else {
        await sc.sites.get({ siteUrl: site.searchConsoleSiteUrl });
        gsc = "ok";
      }
    } catch (err) {
      gsc = humanizeGoogleError(err);
    }
  }

  // === Analytics 4 ===
  let ga4: GoogleCheck;
  if (!site.ga4PropertyId) {
    ga4 = "missing_config";
  } else {
    try {
      const ga = await getAnalyticsDataClient();
      if (!ga) {
        ga4 = "no_credentials";
      } else {
        // Petit run report sur 1 jour : valide l'acces sans charger de donnees.
        await ga.properties.runReport({
          property: `properties/${site.ga4PropertyId}`,
          requestBody: {
            dateRanges: [{ startDate: "yesterday", endDate: "yesterday" }],
            metrics: [{ name: "sessions" }],
            limit: "1",
          },
        });
        ga4 = "ok";
      }
    } catch (err) {
      ga4 = humanizeGoogleError(err);
    }
  }

  return { searchConsole: gsc, analytics: ga4 };
}

function humanizeGoogleError(err: unknown): string {
  if (typeof err === "object" && err !== null) {
    const e = err as { code?: number; message?: string; errors?: Array<{ message?: string }> };
    if (e.code === 403) return "Acces refuse : ajoutez le service account dans la propriete.";
    if (e.code === 404) return "Propriete introuvable. Verifiez l'identifiant saisi.";
    const msg = e.errors?.[0]?.message ?? e.message;
    if (msg) return msg;
  }
  return "Erreur inconnue. Consultez les logs serveur.";
}

export async function deleteSite(siteId: string): Promise<SiteActionResult> {
  await requireAdmin();

  const existing = await db
    .select({ id: clientSite.id, clientAccountId: clientSite.clientAccountId })
    .from(clientSite)
    .where(eq(clientSite.id, siteId))
    .limit(1);
  if (existing.length === 0) return { ok: false, error: "Site introuvable." };

  await db.delete(clientSite).where(eq(clientSite.id, siteId));

  revalidatePath(`/atelier-novelia/clients/${existing[0].clientAccountId}`);
  return { ok: true };
}
