-- Custom migration (audit P4) — provisioning des partitions mensuelles GSC.
-- 0050 a pré-créé 72 partitions RANGE(date) par mois (2026-01 → 2028-12) pour
-- gsc_query_snapshots / gsc_page_snapshots, mais aucun job ne crée les
-- suivantes : dès 2029-01 les upserts du worker Rust routent SILENCIEUSEMENT
-- vers la partition DEFAULT (pruning perdu, rattrapage ultérieur sous verrou
-- ACCESS EXCLUSIVE). pg_partman n'est pas installé sur le serveur prod et
-- pg_cron exige shared_preload_libraries (non garanti) — on ne dépend d'aucun
-- des deux. Remède portable : une fonction idempotente rejouable par un
-- script bun + timer systemd mensuel (scripts/gsc-ensure-partitions.ts,
-- docs/project/ops/gsc-partitions.md), plus un canari sur les DEFAULT.
--
-- Pas de snapshot.json : ces deux fonctions ne changent aucun schéma modélisé
-- par domain-schema.ts (même pattern que 0024/0055, DDL non-drizzle-kit).

-- ensure_gsc_partitions(months_ahead) : pour gsc_query_snapshots ET
-- gsc_page_snapshots, crée (si absente) la partition du mois courant jusqu'à
-- +months_ahead mois. Noms IDENTIQUES à la convention 0050
-- (<table>_YYYY_MM) : les 72 partitions pré-créées sont donc reconnues comme
-- déjà existantes et jamais recréées.
--
-- Idempotence : to_regclass() vérifie d'abord, par nom exact, si la
-- partition du mois existe déjà (rapide, catalogue indexé) — c'est le
-- chemin emprunté par toutes les partitions déjà pré-créées par 0050 et par
-- tout rejeu de cette fonction. En clause de secours, deux exceptions sont
-- attrapées par mois (jamais pour toute la fonction) :
--   - duplicate_table   : la partition a été créée entre notre vérification
--                         et le CREATE (concurrence — deux jobs qui se
--                         chevauchent) → traité comme "exists".
--   - invalid_object_definition (42P17) : la plage de ce mois chevauche une
--     partition déjà enregistrée sous un AUTRE nom que notre convention
--     (partition ad-hoc créée manuellement) → on avertit et on continue avec
--     le mois suivant plutôt que d'échouer tout le run.
CREATE OR REPLACE FUNCTION ensure_gsc_partitions(months_ahead integer DEFAULT 6)
RETURNS TABLE (partition_name text, action text)
LANGUAGE plpgsql
SET search_path = public, pg_catalog
AS $$
DECLARE
  parents CONSTANT text[] := ARRAY['gsc_query_snapshots', 'gsc_page_snapshots'];
  parent text;
  from_bound date;
  to_bound date;
  horizon date;
  part_name text;
  already_exists boolean;
BEGIN
  IF months_ahead < 0 THEN
    RAISE EXCEPTION 'ensure_gsc_partitions: months_ahead must be >= 0, got %', months_ahead;
  END IF;

  horizon := date_trunc('month', now())::date + (months_ahead || ' months')::interval;

  FOREACH parent IN ARRAY parents LOOP
    from_bound := date_trunc('month', now())::date;
    WHILE from_bound <= horizon LOOP
      to_bound := (from_bound + interval '1 month')::date;
      part_name := parent || '_' || to_char(from_bound, 'YYYY_MM');

      SELECT to_regclass(format('public.%I', part_name)) IS NOT NULL INTO already_exists;

      IF already_exists THEN
        partition_name := part_name;
        action := 'exists';
        RETURN NEXT;
      ELSE
        BEGIN
          EXECUTE format(
            'CREATE TABLE %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
            part_name, parent, from_bound, to_bound
          );
          partition_name := part_name;
          action := 'created';
          RETURN NEXT;
        EXCEPTION
          WHEN duplicate_table THEN
            partition_name := part_name;
            action := 'exists';
            RETURN NEXT;
          WHEN invalid_object_definition THEN
            partition_name := part_name;
            action := 'overlap_skipped';
            RAISE WARNING 'ensure_gsc_partitions: % ([%, %)) chevauche une partition de % déjà enregistrée sous un autre nom — ignoré, investiguer manuellement',
              part_name, from_bound, to_bound, parent;
            RETURN NEXT;
        END;
      END IF;

      from_bound := to_bound;
    END LOOP;
  END LOOP;
END;
$$;
--> statement-breakpoint

-- gsc_default_partition_rowcounts() : canari. Un count > 0 sur l'une des deux
-- DEFAULT signifie que des lignes ont fui faute de partition pour leur mois —
-- symptôme direct de l'horizon de provisioning dépassé.
CREATE OR REPLACE FUNCTION gsc_default_partition_rowcounts()
RETURNS TABLE (partition_name text, row_count bigint)
LANGUAGE sql
STABLE
SET search_path = public, pg_catalog
AS $$
  SELECT 'gsc_query_snapshots_default'::text, count(*) FROM gsc_query_snapshots_default
  UNION ALL
  SELECT 'gsc_page_snapshots_default'::text, count(*) FROM gsc_page_snapshots_default;
$$;
