-- 2026-05-21: enforce unique mooring numbers per port at the DB level.
--
-- The canonical mooring regex is `^[A-Z]+\d+$` (e.g. `A1`, `B12`) and
-- the UI gates on uniqueness, but no DB-level constraint existed -
-- which led to a duplicate E17 row in port-nimara surfaced during UAT.
-- Partial unique index lets archived rows reuse a mooring (an old A1
-- can be re-issued after the original is archived).
--
-- Before applying: any existing duplicates must be resolved. The
-- backfill below merges duplicate E17-style rows into a single canonical
-- row, preferring the one with a price (real berth) over the empty
-- shadow. If multiple price-bearing rows exist, the earliest-created
-- wins and the others archive.

-- Phase 1: dedupe. Pick a canonical id per (port_id, mooring_number)
-- where archived_at IS NULL.
WITH ranked AS (
  SELECT
    id,
    port_id,
    mooring_number,
    price,
    created_at,
    ROW_NUMBER() OVER (
      PARTITION BY port_id, mooring_number
      ORDER BY
        -- Prefer rows that have a price (real berths)
        CASE WHEN price IS NOT NULL THEN 0 ELSE 1 END,
        -- Then earliest-created so the audit trail stays consistent
        created_at ASC,
        id ASC
    ) AS rn
  FROM berths
  WHERE archived_at IS NULL
)
UPDATE berths
SET
  archived_at = now(),
  archive_reason = 'Auto-archived 2026-05-21: duplicate of canonical row (mooring uniqueness migration)'
WHERE id IN (
  SELECT id FROM ranked WHERE rn > 1
);

-- Phase 2: the partial unique index. Excludes archived rows so old
-- moorings can be reissued; includes everything else.
CREATE UNIQUE INDEX IF NOT EXISTS uniq_berths_port_mooring_active
  ON berths (port_id, mooring_number)
  WHERE archived_at IS NULL;
