-- Prod-readiness audit 2026-05-11 follow-ups (A5 + Audit-17 G-I4 + perf).
-- Fully idempotent - safe to re-run.
--
-- IMPORTANT: This migration creates indexes CONCURRENTLY, which Postgres
-- forbids inside a transaction block. When applying via `psql`, do NOT
-- wrap the file in `BEGIN/COMMIT`. The DO blocks (for constraint adds)
-- each open their own implicit transaction, which is fine.
--
-- ─── A5: tighten chk_system_folder_shape (NULL escape) ────────────────────
-- The constraint installed by 0051 evaluates to NULL when entity_type IS
-- NULL AND system_managed = true. Postgres treats NULL as "not false" so
-- the constraint passes. Tighten the predicate so system_managed=true
-- always implies a non-null entity_type.
-- Drop the old constraint (if present from 0051) then re-create with the
-- tightened predicate. Wrapped in DO blocks for idempotency.

DO $$ BEGIN
  IF EXISTS (
    SELECT 1 FROM pg_constraint WHERE conname = 'chk_system_folder_shape'
  ) THEN
    ALTER TABLE "document_folders"
      DROP CONSTRAINT "chk_system_folder_shape";
  END IF;
END $$;

DO $$ BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM pg_constraint WHERE conname = 'chk_system_folder_shape'
  ) THEN
    ALTER TABLE "document_folders"
      ADD CONSTRAINT "chk_system_folder_shape" CHECK (
        NOT system_managed
        OR (
          entity_type IS NOT NULL
          AND (
            entity_type = 'root'
            OR (
              entity_type = ANY(ARRAY['client','company','yacht'])
              AND entity_id IS NOT NULL
            )
          )
        )
      );
  END IF;
END $$;

-- ─── Audit-17 G-I4: berth_recommendations.interest_id missing FK ──────────
-- The column has carried an `// references interests.id` comment since
-- introduction but no actual constraint. If an interest is hard-deleted,
-- stale berth_recommendations rows persist and skew the recommender tier
-- aggregates. Cascade delete keeps the dependent rows in sync with the
-- parent interest.

DO $$ BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM pg_constraint
    WHERE conname = 'berth_recommendations_interest_id_fkey'
  ) THEN
    ALTER TABLE "berth_recommendations"
      ADD CONSTRAINT "berth_recommendations_interest_id_fkey"
      FOREIGN KEY ("interest_id") REFERENCES "interests" ("id")
      ON DELETE CASCADE NOT VALID;
    ALTER TABLE "berth_recommendations"
      VALIDATE CONSTRAINT "berth_recommendations_interest_id_fkey";
  END IF;
END $$;

-- ─── Performance: composite indexes for the aggregated projection ─────────
-- listFilesAggregatedByEntity / listInflightWorkflowsAggregatedByEntity
-- always filter by port_id (defense-in-depth at every join) and then by
-- one of client_id / company_id / yacht_id. The existing single-column
-- indexes (idx_files_client etc.) don't include port_id so Postgres
-- either re-checks per row or falls back to a sequential scan once the
-- per-port row counts grow. Add the composites BEFORE the prod backfill
-- so the rebuild rides the new indexes.
--
-- CONCURRENTLY avoids the ShareLock that blocks writes during a normal
-- CREATE INDEX. It can fail mid-build - IF NOT EXISTS skips on re-run,
-- but a failed/invalid index from a prior attempt needs to be dropped
-- before this migration succeeds (check `pg_indexes` + `pg_index.indisvalid`).

CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_files_port_client"
  ON "files" ("port_id", "client_id");
CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_files_port_company"
  ON "files" ("port_id", "company_id");
CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_files_port_yacht"
  ON "files" ("port_id", "yacht_id");

CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_docs_port_client"
  ON "documents" ("port_id", "client_id");
CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_docs_port_company"
  ON "documents" ("port_id", "company_id");
CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_docs_port_yacht"
  ON "documents" ("port_id", "yacht_id");
