-- t-paliad-246 / m/paliad#77 — Backup Mode catalog table.
--
-- Design: docs/design-backup-mode-2026-05-25.md §4. One row per backup
-- run (on-demand or scheduled). The catalog is operational metadata for
-- the /admin/backups UI (size, row counts, storage URI, status). The
-- audit chain stays on paliad.system_audit_log — this table is the
-- richer-shape duplicate that the UI lists from without parsing JSON.
--
-- INSERT/UPDATE happen only through the Go service path (BackupRunner)
-- under the migration-runner role, so we don't add a write RLS policy
-- for end users. SELECT is admin-only, mirroring system_audit_log.
--
-- Idempotent: CREATE TABLE / INDEX / POLICY all guarded.

SELECT set_config(
    'paliad.audit_reason',
    'mig 123: add paliad.backups catalog for Backup Mode (t-paliad-246 / m/paliad#77 Slice A)',
    true);

CREATE TABLE IF NOT EXISTS paliad.backups (
    id                  uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    kind                text NOT NULL CHECK (kind IN ('scheduled', 'on_demand')),
    status              text NOT NULL CHECK (status IN ('running', 'done', 'failed')),
    -- requested_by is NULL for kind='scheduled' (no human caller).
    requested_by        uuid REFERENCES paliad.users(id) ON DELETE SET NULL,
    -- requested_by_email is captured at write time so the row survives
    -- a subsequent user deletion. For scheduled runs we write a sentinel
    -- like 'system@paliad' (no real user attached).
    requested_by_email  text NOT NULL,
    -- audit_id back-references the system_audit_log row written before
    -- the artifact is generated. Nullable so a catalog row can still be
    -- INSERTed if the audit write itself fails (defense-in-depth).
    audit_id            uuid REFERENCES paliad.system_audit_log(id) ON DELETE SET NULL,
    -- storage_uri is populated when status flips to 'done'. Resolves
    -- through the Go-side ArtifactStore interface ('file://...' for
    -- LocalDiskStore today; future stores get their own URI scheme).
    storage_uri         text,
    size_bytes          bigint,
    row_counts          jsonb NOT NULL DEFAULT '{}'::jsonb,
    sheet_count         int,
    warnings            jsonb NOT NULL DEFAULT '[]'::jsonb,
    -- error is NULL unless status='failed'. Free-form, captured from
    -- the Go-side error.Error().
    error               text,
    started_at          timestamptz NOT NULL DEFAULT now(),
    finished_at         timestamptz,
    -- deleted_at marks artifacts the lifecycle cleanup removed from
    -- storage (Slice B). The catalog row itself stays forever — it's
    -- part of the audit chain. NULL means "still on disk".
    deleted_at          timestamptz
);

-- Read patterns:
--   - "show me recent backups" — started_at DESC
--   - "find last successful scheduled backup today" — kind + status + started_at
CREATE INDEX IF NOT EXISTS backups_started_at_desc_idx
    ON paliad.backups (started_at DESC);

CREATE INDEX IF NOT EXISTS backups_kind_status_idx
    ON paliad.backups (kind, status);

ALTER TABLE paliad.backups ENABLE ROW LEVEL SECURITY;

-- Admin-only read. INSERT/UPDATE/DELETE happen via the Go service path
-- under the migration-runner role (no end-user write surface).
DROP POLICY IF EXISTS backups_select_admin ON paliad.backups;
CREATE POLICY backups_select_admin ON paliad.backups
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM paliad.users u
             WHERE u.id = auth.uid()
               AND u.global_role = 'global_admin'
        )
    );

COMMENT ON TABLE paliad.backups IS
    'Catalog of org-scope backup runs (t-paliad-246 / m/paliad#77). One row per scheduled or on-demand backup. status transitions: running → done | failed. storage_uri is resolved by the Go-side ArtifactStore interface. audit_id links to system_audit_log; the catalog row is the richer-shape duplicate, the audit row is the trust signal.';

COMMENT ON COLUMN paliad.backups.requested_by_email IS
    'Captured at write time so the row survives user deletion. Sentinel ''system@paliad'' for scheduled runs.';

COMMENT ON COLUMN paliad.backups.storage_uri IS
    'Resolved by the Go-side ArtifactStore implementation. file://... for LocalDiskStore; future stores use their own URI scheme.';

COMMENT ON COLUMN paliad.backups.deleted_at IS
    'Set when the artifact is removed from storage by lifecycle cleanup. Catalog row stays forever (audit chain). NULL means artifact is still on disk.';
