-- Sender-Key (Signal-style) multi-device crypto.
--
-- Replaces per-device envelopes with a per-conversation symmetric key.
-- Each device that should be able to read a conversation gets one row in
-- `conversation_keys` containing the conv-key wrapped to its X25519 pubkey.
--
-- Senders encrypt the message body once with the conv-key (XSalsa20-Poly1305
-- secretbox) instead of N times to N device pubkeys. This makes new-device
-- onboarding for existing conversations possible: any existing device can
-- wrap the conv-key for a freshly-registered device, and that device can then
-- decrypt the entire history without back-population.
--
-- Pre-launch wipe: existing messages + envelopes are dropped (the old
-- per-device envelopes can't be re-encoded into the new format without the
-- sender's private key, which lives only on each user's device).

-- 1) Wipe legacy message data --------------------------------------------------

drop table if exists public.message_envelopes cascade;
truncate table
  public.message_reactions,
  public.message_reads,
  public.message_attachments,
  public.messages
  restart identity cascade;

-- 2) Add ciphertext + nonce + key_version columns to messages -----------------

alter table public.messages
  add column if not exists ciphertext bytea,
  add column if not exists nonce bytea,
  add column if not exists key_version int;

-- Backfill not needed because we just truncated. Make NOT NULL going forward.
alter table public.messages
  alter column ciphertext set not null,
  alter column nonce set not null,
  alter column key_version set not null,
  alter column key_version set default 1;

-- 3) Track currently-active key version per conversation ----------------------

alter table public.conversations
  add column if not exists active_key_version int not null default 1;

-- 4) conversation_keys table ---------------------------------------------------
--
-- One row per (conversation, recipient_device, key_version). The wrapped key
-- is encrypted with `crypto_box`/`box`-style asymmetric crypto (X25519 +
-- XSalsa20-Poly1305) — sender_device's private key + recipient_device's
-- public key derive a shared secret to decrypt the embedded conv-key.

create table if not exists public.conversation_keys (
  conversation_id      uuid not null references public.conversations(id) on delete cascade,
  recipient_device_id  uuid not null references public.devices(id)        on delete cascade,
  key_version          int  not null,
  sender_device_id     uuid not null references public.devices(id)        on delete restrict,
  encrypted_key        bytea not null,
  nonce                bytea not null,
  created_at           timestamptz not null default now(),
  primary key (conversation_id, recipient_device_id, key_version)
);

create index if not exists conversation_keys_recipient_idx
  on public.conversation_keys(recipient_device_id);

create index if not exists conversation_keys_conv_version_idx
  on public.conversation_keys(conversation_id, key_version);

-- 5) RLS policies for conversation_keys ---------------------------------------

alter table public.conversation_keys enable row level security;

-- A user can SELECT a row only if the recipient device belongs to them.
drop policy if exists conversation_keys_select_owner on public.conversation_keys;
create policy conversation_keys_select_owner
  on public.conversation_keys
  for select
  to authenticated
  using (
    exists (
      select 1
        from public.devices d
       where d.id = recipient_device_id
         and d.user_id = auth.uid()
    )
  );

-- INSERT allowed only by an authenticated user who:
--   (a) is a member of the conversation,
--   (b) owns the sender_device_id (so a 3rd party can't impersonate),
--   (c) the recipient_device belongs to a member of the same conversation.
drop policy if exists conversation_keys_insert_member on public.conversation_keys;
create policy conversation_keys_insert_member
  on public.conversation_keys
  for insert
  to authenticated
  with check (
    exists (
      select 1
        from public.conversation_members m
       where m.conversation_id = conversation_keys.conversation_id
         and m.user_id = auth.uid()
         and m.accepted = true
    )
    and exists (
      select 1
        from public.devices d
       where d.id = sender_device_id
         and d.user_id = auth.uid()
    )
    and exists (
      select 1
        from public.devices d
        join public.conversation_members m
          on m.user_id = d.user_id
         and m.conversation_id = conversation_keys.conversation_id
       where d.id = recipient_device_id
         and m.accepted = true
    )
  );

-- DELETE: only sender (cleanup, e.g. when a device is removed). Rare.
drop policy if exists conversation_keys_delete_sender on public.conversation_keys;
create policy conversation_keys_delete_sender
  on public.conversation_keys
  for delete
  to authenticated
  using (
    exists (
      select 1
        from public.devices d
       where d.id = sender_device_id
         and d.user_id = auth.uid()
    )
  );

-- 6) Realtime publication ------------------------------------------------------
--
-- Devices subscribe to INSERTs on `conversation_keys` so a freshly-registered
-- device sees its key bundles arrive. They also already subscribe to `devices`
-- INSERTs to know when peers join a conversation; that's set up in earlier
-- migrations.

alter publication supabase_realtime add table public.conversation_keys;
