import "server-only";
import { getPool, query } from "../connection";

export type PlanRow = {
  id: number;
  code: string;
  name: string;
  description: string | null;
  monthly_price: string; // decimal renvoyé en string par mysql2
  yearly_price: string;
  max_technicians: number;
  max_sites: number;
  max_machines: number;
  max_templates: number;
  max_admins: number; // 1 / 3 / -1 — gating Technicien::create role=admin (mf_ask #63)
  // Colonnes JSON côté MySQL : mysql2 les décode automatiquement en JS
  // (array ou object). On types `unknown` et on délègue à des parsers
  // défensifs côté React qui gèrent string (legacy / compat) ET array (cas
  // courant depuis l'extension #59). Cf. bug #68 : un type `string | null`
  // faisait planter `JSON.parse(array)` en silencieux → modale vide.
  features: unknown;
  visible: number;
  sort_order: number;
  // Champs marketing (cf. mf_ask #57/#59 — source unique pour panel + signup +
  // upgrade + vitrine Astro). `marketing_bullets` est distinct du JSON
  // `features` (codes techniques de gating) et du `plan_features_catalog.label`
  // (libellé partagé d'un code) — c'est le bullet libre, par plan, du vitrine.
  marketing_bullets: unknown; // colonne JSON, cf. note features
  cta_label: string | null;
  highlighted: number; // 0 ou 1 — un seul plan à 1 à la fois (cf. updatePlan)
  // Compteur dérivé : nombre de subscriptions actives sur ce plan
  active_subscriptions: number;
};

// `isPerSeatPlan` vit dans `@/lib/plans-shared` (client-safe) — réexporté ici
// pour que les consommateurs serveur (Route Handler) gardent un import unique.
export { isPerSeatPlan } from "@/lib/plans-shared";

// ---------------------------------------------------------------------------
// Mutations
// ---------------------------------------------------------------------------
//
// Le panel a un GRANT UPDATE ciblé sur la table `plans` (cf. AGENTS.md
// "Périmètre des mutations admin"). Toute autre table reste read-only pour
// le panel — pour suspendre/cancel un tenant, etc., on passe par
// missioflow-app.
//
// Tous les champs sont passés via `?` placeholders, jamais de concat avec
// l'input utilisateur. Validation des types côté Route Handler avant
// l'appel ici.

// Matrice acrtée 2026-05-09 (cf. mf_answer #10 + AGENTS.md "Périmètre des
// mutations admin") : les champs `code`, `stripe_price_id_*`, `id`,
// `created_at`, `updated_at`, `sort_order` ne sont JAMAIS dans cette liste.
// Renommer un `code` après le 1er signup casse l'activation tenant via
// webhook Stripe ; les `stripe_price_id_*` sont gérés par
// bin/setup_stripe.php ; le reste = métadonnées internes.
export type UpdatablePlanFields = Partial<{
  name: string;
  description: string | null;
  monthly_price: number;
  yearly_price: number;
  max_technicians: number;
  max_sites: number;
  max_machines: number;
  max_templates: number;
  max_admins: number;
  features: string[]; // sera serialisé en JSON
  visible: boolean;
  marketing_bullets: string[]; // sera serialisé en JSON (cf. updatePlan)
  cta_label: string | null;
  highlighted: boolean;
}>;

const PLAN_SELECT_COLS = `
  p.id, p.code, p.name, p.description, p.monthly_price, p.yearly_price,
  p.max_technicians, p.max_sites, p.max_machines, p.max_templates, p.max_admins,
  p.features, p.visible, p.sort_order,
  p.marketing_bullets, p.cta_label, p.highlighted,
  (SELECT COUNT(*) FROM tenant_subscriptions ts
    WHERE ts.plan_id = p.id AND ts.status IN ('trialing','active','past_due')
  ) AS active_subscriptions
`;

export async function getPlan(id: number): Promise<PlanRow | null> {
  const rows = await query<PlanRow>(
    `SELECT ${PLAN_SELECT_COLS} FROM plans p WHERE p.id = ? LIMIT 1`,
    [id],
  );
  return rows[0] ?? null;
}

export async function updatePlan(
  id: number,
  fields: UpdatablePlanFields,
): Promise<void> {
  // On construit dynamiquement la liste des SET — clés whitelisted ci-dessous
  // pour éviter qu'un caller passe `password_hash` ou autre.
  const ALLOWED = [
    "name",
    "description",
    "monthly_price",
    "yearly_price",
    "max_technicians",
    "max_sites",
    "max_machines",
    "max_templates",
    "max_admins",
    "features",
    "visible",
    "marketing_bullets",
    "cta_label",
    "highlighted",
  ] as const;

  const setParts: string[] = [];
  const params: unknown[] = [];
  for (const key of ALLOWED) {
    if (!(key in fields)) continue;
    const value = fields[key];
    if (value === undefined) continue;
    setParts.push(`${key} = ?`);
    if (key === "features" || key === "marketing_bullets") {
      params.push(JSON.stringify(value ?? []));
    } else if (key === "visible" || key === "highlighted") {
      params.push(value ? 1 : 0);
    } else {
      params.push(value);
    }
  }

  if (setParts.length === 0) {
    throw new Error("Aucun champ à mettre à jour.");
  }
  params.push(id);

  // Cas spécial : si on monte `highlighted` à true, on garantit l'unicité du
  // badge "Le plus populaire" en démarquant les autres plans dans la même
  // transaction (cf. mf_answer #60). Pas de trigger DB — la logique métier
  // reste lisible côté panel et l'audit log capture proprement le déplacement.
  if (fields.highlighted === true) {
    const conn = await getPool().getConnection();
    try {
      await conn.beginTransaction();
      await conn.query(
        "UPDATE plans SET highlighted = 0 WHERE highlighted = 1 AND id <> ?",
        [id],
      );
      await conn.query(
        `UPDATE plans SET ${setParts.join(", ")} WHERE id = ?`,
        params,
      );
      await conn.commit();
    } catch (err) {
      await conn.rollback();
      throw err;
    } finally {
      conn.release();
    }
    return;
  }

  await query(`UPDATE plans SET ${setParts.join(", ")} WHERE id = ?`, params);
}

// ---------------------------------------------------------------------------

export async function listPlans(): Promise<PlanRow[]> {
  return query<PlanRow>(
    `SELECT ${PLAN_SELECT_COLS} FROM plans p ORDER BY p.sort_order ASC, p.id ASC`,
  );
}
