  -- ============================================================================
  --  FIDÉLITÉ — Schéma Supabase (Postgres)
  --  À coller dans Supabase → SQL Editor → New query → Run.
  --  Idempotent : on peut le relancer sans casser l'existant.
  -- ============================================================================

  -- ----------------------------------------------------------------------------
  -- 1. Fonction de génération de code (utilisée en défaut de la table clients,
  --    donc définie AVANT la table).
  -- ----------------------------------------------------------------------------

  -- Génère un code client unique du type « FID-3F9K2A » (sans I, O, 0, 1).
  create or replace function public.generer_code_client()
  returns text
  language plpgsql
  set search_path = public
  as $$
  declare
    chars        text := 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    nouveau_code text;
    i            int;
  begin
    loop
      nouveau_code := 'FID-';
      for i in 1..6 loop
        nouveau_code := nouveau_code ||
          substr(chars, floor(random() * length(chars))::int + 1, 1);
      end loop;
      -- variable renommée pour éviter l'ambiguïté avec la colonne clients.code
      exit when not exists
        (select 1 from public.clients c where c.code = nouveau_code);
    end loop;
    return nouveau_code;
  end;
  $$;

  -- ----------------------------------------------------------------------------
  -- 2. Tables
  -- ----------------------------------------------------------------------------

  -- Personnel du magasin (droits d'écriture). On y ajoute l'ID d'un compte auth
  -- pour qu'il devienne « staff » (voir SETUP.md).
  create table if not exists public.staff (
    user_id    uuid primary key references auth.users(id) on delete cascade,
    created_at timestamptz not null default now()
  );

  -- Un client fidélité. Lié à un compte de connexion (auth.users) quand le client
  -- s'inscrit lui-même depuis l'app client. user_id peut être null si le staff
  -- crée un client « au comptoir » sans compte.
  create table if not exists public.clients (
    id         uuid primary key default gen_random_uuid(),
    user_id    uuid unique references auth.users(id) on delete set null,
    code       text unique not null default public.generer_code_client(),
    nom        text not null,
    prenom     text,
    telephone  text,
    points     numeric not null default 0,
    created_at timestamptz not null default now()
  );
  -- Ajout de la colonne prénom si la table existait déjà (migration).
  alter table public.clients add column if not exists prenom text;

  -- Historique : gain (facture) / dépense (récompense) / ajustement.
  create table if not exists public.mouvements (
    id            uuid primary key default gen_random_uuid(),
    client_id     uuid not null references public.clients(id) on delete cascade,
    type          text not null check (type in ('facture','recompense','ajustement')),
    montant_euros numeric,
    points        numeric not null default 0,
    libelle       text not null default '',
    created_at    timestamptz not null default now()
  );
  create index if not exists idx_mouvements_client on public.mouvements(client_id);

  -- Catalogue des récompenses.
  create table if not exists public.recompenses (
    id          uuid primary key default gen_random_uuid(),
    nom         text not null,
    cout_points numeric not null default 0,
    actif       boolean not null default true,
    created_at  timestamptz not null default now()
  );

  -- Réglages globaux du magasin (une seule ligne, id = 1).
  create table if not exists public.reglages (
    id              int primary key default 1 check (id = 1),
    euros_par_point numeric not null default 10,
    nom_magasin     text not null default ''
  );
  insert into public.reglages (id) values (1) on conflict (id) do nothing;

  -- ----------------------------------------------------------------------------
  -- 3. Fonctions & triggers de logique métier
  -- ----------------------------------------------------------------------------

  -- Vrai si l'utilisateur connecté fait partie du personnel.
  -- SECURITY DEFINER pour pouvoir lire la table staff malgré la RLS.
  create or replace function public.est_staff()
  returns boolean
  language sql
  security definer
  stable
  set search_path = public
  as $$
    select exists (select 1 from public.staff s where s.user_id = auth.uid());
  $$;

  -- Tient à jour le solde de points du client à chaque mouvement, pour que le
  -- solde reste fiable quelle que soit l'app qui écrit.
  create or replace function public.maj_points()
  returns trigger
  language plpgsql
  security definer
  set search_path = public
  as $$
  begin
    if (tg_op = 'INSERT') then
      update public.clients set points = points + new.points where id = new.client_id;
    elsif (tg_op = 'UPDATE') then
      update public.clients set points = points - old.points + new.points
        where id = new.client_id;
    elsif (tg_op = 'DELETE') then
      update public.clients set points = points - old.points where id = old.client_id;
    end if;
    return null;
  end;
  $$;

  drop trigger if exists trg_maj_points on public.mouvements;
  create trigger trg_maj_points
    after insert or update or delete on public.mouvements
    for each row execute function public.maj_points();

  -- Empêche un client (non-staff) de modifier son solde, son code ou son user_id.
  create or replace function public.proteger_client()
  returns trigger
  language plpgsql
  security definer
  set search_path = public
  as $$
  begin
    if not public.est_staff() then
      new.points  := old.points;
      new.code    := old.code;
      new.user_id := old.user_id;
    end if;
    return new;
  end;
  $$;

  drop trigger if exists trg_proteger_client on public.clients;
  create trigger trg_proteger_client
    before update on public.clients
    for each row execute function public.proteger_client();

  -- ----------------------------------------------------------------------------
  -- 4. Sécurité (Row Level Security)
  --    Client = lit uniquement SES données. Staff = accès total.
  -- ----------------------------------------------------------------------------

  alter table public.staff       enable row level security;
  alter table public.clients     enable row level security;
  alter table public.mouvements  enable row level security;
  alter table public.recompenses enable row level security;
  alter table public.reglages    enable row level security;

  -- staff : seul le staff peut lire la liste (personne ne s'auto-déclare staff).
  drop policy if exists staff_select on public.staff;
  create policy staff_select on public.staff
    for select using (public.est_staff());

  -- clients
  drop policy if exists clients_select on public.clients;
  create policy clients_select on public.clients
    for select using (public.est_staff() or user_id = auth.uid());

  drop policy if exists clients_insert on public.clients;
  create policy clients_insert on public.clients
    for insert with check (public.est_staff() or user_id = auth.uid());

  drop policy if exists clients_update on public.clients;
  create policy clients_update on public.clients
    for update using (public.est_staff() or user_id = auth.uid());

  drop policy if exists clients_delete on public.clients;
  create policy clients_delete on public.clients
    for delete using (public.est_staff());

  -- mouvements : le client lit les siens ; seul le staff écrit (anti-triche).
  drop policy if exists mouvements_select on public.mouvements;
  create policy mouvements_select on public.mouvements
    for select using (
      public.est_staff()
      or client_id in (select id from public.clients where user_id = auth.uid())
    );

  drop policy if exists mouvements_insert on public.mouvements;
  create policy mouvements_insert on public.mouvements
    for insert with check (public.est_staff());

  drop policy if exists mouvements_update on public.mouvements;
  create policy mouvements_update on public.mouvements
    for update using (public.est_staff()) with check (public.est_staff());

  drop policy if exists mouvements_delete on public.mouvements;
  create policy mouvements_delete on public.mouvements
    for delete using (public.est_staff());

  -- recompenses : tout compte connecté lit ; seul le staff modifie.
  drop policy if exists recompenses_select on public.recompenses;
  create policy recompenses_select on public.recompenses
    for select using (auth.role() = 'authenticated');

  drop policy if exists recompenses_write on public.recompenses;
  create policy recompenses_write on public.recompenses
    for all using (public.est_staff()) with check (public.est_staff());

  -- reglages : tout compte connecté lit ; seul le staff modifie.
  drop policy if exists reglages_select on public.reglages;
  create policy reglages_select on public.reglages
    for select using (auth.role() = 'authenticated');

  drop policy if exists reglages_update on public.reglages;
  create policy reglages_update on public.reglages
    for update using (public.est_staff()) with check (public.est_staff());
