-- AZ-503-foundation: deterministic tile identity (UUIDv5) + multi-flight evidence preservation.
--
-- Adds four columns to `tiles`:
--   - flight_id (uuid NULL)         — per-UAV-flight identifier. NULL for google_maps and
--                                     legacy UAV rows; populated for AZ-503+ UAV uploads.
--   - location_hash (uuid NOT NULL) — UUIDv5(TILE_NAMESPACE, "{tile_zoom}/{tile_x}/{tile_y}").
--                                     Drives leaflet hot-path lookups and future voting layer.
--   - content_sha256 (bytea NULL)   — SHA-256 of the JPEG body at insert time. NULL for legacy
--                                     rows (pre-AZ-503), NOT NULL for new rows enforced at the
--                                     application layer (TileEntity / repositories). Kept NULL-able
--                                     at the column level because the migration cannot read tile
--                                     files from disk safely (path may have moved, file may be
--                                     gone). Application invariant: SHA-256 only meaningful when
--                                     not NULL.
--   - legacy_id (uuid NULL)         — preserves the pre-AZ-503 random id of each row for one
--                                     deprecation cycle (per AZ-503 Risk 1). Dropped in a
--                                     follow-up migration once external references to legacy
--                                     ids are confirmed flushed.
--
-- Switches the UPSERT conflict key from (latitude, longitude, tile_zoom, tile_size_meters, source)
-- to an integer-only key with per-flight separation:
--   (tile_zoom, tile_x, tile_y, tile_size_meters, source, COALESCE(flight_id, '00000000-...'::uuid))
-- so two UAV flights uploading the same (z, x, y) cell coexist as distinct rows.
--
-- TILE_NAMESPACE is pinned cross-repo at 5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c (matches
-- SatelliteProvider.Common.Utils.Uuidv5.TileNamespace and gps-denied-onboard
-- c6_tile_cache/_uuid.py). DO NOT change without updating both sides.
--
-- Whole migration runs inside one transaction; partial failure leaves the table without the
-- new columns rather than half-migrated (per AZ-484 precedent for tile-table migrations).

BEGIN;

CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- Helper: pure-SQL UUIDv5 (SHA-1-based, RFC 9562 §5.5). Used ONLY for the
-- location_hash backfill below. Application writes compute the same UUIDv5
-- via SatelliteProvider.Common.Utils.Uuidv5.Create (verified byte-identical
-- against Python uuid.uuid5 in AZ-503 AC-1).
CREATE OR REPLACE FUNCTION pg_temp.uuidv5(namespace_uuid uuid, name text) RETURNS uuid AS $$
DECLARE
    ns_bytes  bytea;
    hash      bytea;
    b6        int;
    b8        int;
BEGIN
    -- Namespace UUID as 16 big-endian bytes.
    ns_bytes := decode(replace(namespace_uuid::text, '-', ''), 'hex');
    hash := substring(digest(ns_bytes || convert_to(name, 'UTF8'), 'sha1') from 1 for 16);
    -- Set version = 5 (upper nibble of byte 6).
    b6 := (get_byte(hash, 6) & 15) | 80;
    hash := set_byte(hash, 6, b6);
    -- Set RFC 4122 variant (upper 2 bits of byte 8 = 10).
    b8 := (get_byte(hash, 8) & 63) | 128;
    hash := set_byte(hash, 8, b8);
    RETURN encode(hash, 'hex')::uuid;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

ALTER TABLE tiles ADD COLUMN IF NOT EXISTS flight_id uuid;
ALTER TABLE tiles ADD COLUMN IF NOT EXISTS location_hash uuid;
ALTER TABLE tiles ADD COLUMN IF NOT EXISTS content_sha256 bytea;
ALTER TABLE tiles ADD COLUMN IF NOT EXISTS legacy_id uuid;

-- Preserve the pre-AZ-503 random id under legacy_id for the deprecation window.
UPDATE tiles
SET legacy_id = id
WHERE legacy_id IS NULL;

-- Backfill location_hash for every existing row. Deterministic; same algorithm
-- the application uses for new writes.
UPDATE tiles
SET location_hash = pg_temp.uuidv5(
    '5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c'::uuid,
    tile_zoom::text || '/' || tile_x::text || '/' || tile_y::text)
WHERE location_hash IS NULL;

-- location_hash is now populated for every row; promote to NOT NULL.
ALTER TABLE tiles ALTER COLUMN location_hash SET NOT NULL;

-- content_sha256 is intentionally left nullable for legacy rows (the migration cannot
-- safely re-read tile files: paths may have rotated, files may be absent). The application
-- layer enforces NOT NULL for all writes starting at AZ-503; legacy NULLs are treated as
-- "unverified content" and surfaced as such if/when integrity checks are added downstream.

-- Drop AZ-484's lat/lon-keyed unique index and replace with the integer + flight_id key.
DROP INDEX IF EXISTS idx_tiles_unique_location_source;
DROP INDEX IF EXISTS idx_tiles_unique_location;

CREATE UNIQUE INDEX IF NOT EXISTS idx_tiles_unique_identity
    ON tiles (
        tile_zoom,
        tile_x,
        tile_y,
        tile_size_meters,
        source,
        COALESCE(flight_id, '00000000-0000-0000-0000-000000000000'::uuid)
    );

-- Lookup index on location_hash for application reads (kept lightweight here;
-- the larger covering index `tiles_leaflet_path` is owned by AZ-505).
CREATE INDEX IF NOT EXISTS idx_tiles_location_hash ON tiles (location_hash);

COMMIT;
