-- Schritt 2: die Anmeldung braucht einen Weg, ihre Zeile in app_users
-- anzulegen — bevor es einen Sitzungskontext gibt.
--
-- Das ist das Henne-Ei-Problem jeder eigenen Anmeldung: app.user_id kann erst
-- gesetzt werden, wenn die Kennung feststeht, und die entsteht genau hier.
-- Bisher löste ein Dienstschlüssel mit BYPASSRLS solche Fälle. Den gibt es
-- nicht mehr, und er soll auch nicht zurückkommen — eine Verbindung, die
-- alles darf, ist für einen einzigen Schreibvorgang ein zu grosser Hebel.
--
-- Stattdessen: eine SECURITY-DEFINER-Funktion mit genau einer Befugnis.
-- Sie schreibt in app_users und liest lesend in profiles — sonst nichts. Wer
-- sie aufruft, bekommt eine UUID zurück und sonst keine Auskunft.
create or replace function app_upsert_user(
  p_external_id text,
  p_email text,
  p_full_name text
)
returns uuid
language plpgsql
security definer
set search_path = public, pg_temp
as $$
declare
  v_id uuid;
begin
  if p_external_id is null or btrim(p_external_id) = '' then
    raise exception 'Externe Kennung fehlt.';
  end if;
  if p_email is null or btrim(p_email) = '' then
    raise exception 'E-Mail-Adresse fehlt.';
  end if;

  -- Bekanntes Konto: nur nachziehen, was sich beim Anbieter geändert haben
  -- kann. Die Kennung bleibt, auch wenn Name oder Adresse wechseln — daran
  -- hängen Notizen, Entwürfe und Protokolleinträge.
  update app_users
     set email        = p_email,
         full_name    = coalesce(p_full_name, full_name),
         last_seen_at = now()
   where external_id = p_external_id
  returning id into v_id;

  if v_id is not null then
    return v_id;
  end if;

  -- Erstanmeldung. Gibt es zu dieser Adresse bereits ein Profil, wird dessen
  -- Kennung übernommen statt einer neuen: profiles.id ist heute die
  -- auth.users.id, und neun Fremdschlüssel zeigen darauf. Eine frisch
  -- vergebene UUID würde die Person von ihrer eigenen Vorgeschichte trennen
  -- — sie wäre angemeldet, hätte aber weder Rolle noch Freischaltung.
  --
  -- Der Abgleich über die Adresse ist hier vertretbar und sonst nirgends:
  -- die Adresse kommt aus einem von Entra ausgestellten Token, nicht aus
  -- einem Formular. Wer sie behauptet, hat sie bereits bewiesen.
  select p.id into v_id
    from profiles p
   where lower(p.email) = lower(p_email)
   limit 1;

  v_id := coalesce(v_id, gen_random_uuid());

  begin
    insert into app_users (id, external_id, email, full_name, last_seen_at)
    values (v_id, p_external_id, p_email, p_full_name, now());
  exception
    when unique_violation then
      -- Zwei gleichzeitige Erstanmeldungen desselben Kontos. Die zweite
      -- findet die Zeile, die die erste gerade angelegt hat.
      select id into v_id from app_users where external_id = p_external_id;
      if v_id is null then
        raise;
      end if;
  end;

  return v_id;
end;
$$;

comment on function app_upsert_user(text, text, text) is
  'Legt die app_users-Zeile zur Erstanmeldung an und liefert die Kennung. Übernimmt bei bekannter E-Mail die vorhandene profiles.id.';

-- app_users trägt RLS und hat bewusst keine Schreib-Policy: die Anwendung
-- kommt an die Tabelle nur durch diese Funktion. Ein Fehler im Anwendungscode
-- kann dort also nichts anlegen, ändern oder löschen.
do $$
declare r text;
begin
  foreach r in array array['anon', 'authenticated', 'service_role'] loop
    if exists (select 1 from pg_roles where rolname = r) then
      execute format('grant execute on function app_upsert_user(text, text, text) to %I', r);
    end if;
  end loop;
end;
$$;

-- ═══ Gegenprobe ══════════════════════════════════════════════════
-- Zweimal dieselbe externe Kennung muss dieselbe UUID ergeben. Wäre es nicht
-- so, bekäme jede Anmeldung ein neues Konto und niemand behielte seine
-- Rolle — ein Fehler, der sich erst Wochen später als „meine Notizen sind
-- weg" zeigt.
do $$
declare
  v_first uuid;
  v_second uuid;
  v_probe text := 'probe-' || gen_random_uuid()::text;
begin
  v_first := app_upsert_user(v_probe, v_probe || '@example.invalid', 'Probe');
  v_second := app_upsert_user(v_probe, v_probe || '@example.invalid', 'Probe');

  if v_first is distinct from v_second then
    raise exception 'app_upsert_user() vergibt bei zweiter Anmeldung eine neue Kennung — Abbruch.';
  end if;

  delete from app_users where id = v_first;
end;
$$;
