-- ============================================================================
-- GLM HUB — ESQUEMA COMPLETO DE SUPABASE
-- ============================================================================
-- Incluye:
--   1) Acceso automático por dominio, administradores y bloqueos excepcionales.
--   2) Catálogo compartido para todos los usuarios del Hub.
--   3) Favoritos y accesos recientes por usuario.
--   4) Bucket público para los íconos.
--   5) RLS, permisos y Realtime.
--   6) Función segura que usa n8n para validar al administrador.
--   7) Solicitudes de acceso, aprobadores y decisión única.
--
-- Ejecutar el archivo completo una sola vez en:
-- Supabase Studio > SQL Editor > New query > Run.
-- El script es idempotente y puede volver a ejecutarse para restaurar políticas.
-- ============================================================================

begin;

create extension if not exists pgcrypto with schema extensions;

-- --------------------------------------------------------------------------
-- 1. ACCESO POR DOMINIO, ADMINISTRADORES Y BLOQUEOS
-- --------------------------------------------------------------------------
-- Regla vigente:
--   * Todo usuario autenticado con @gomezleemarketing.com entra como member.
--   * Un registro activo con role = 'admin' concede funciones administrativas.
--   * Un registro con is_active = false bloquea excepcionalmente ese correo.
--   * Los usuarios normales no necesitan una fila en esta tabla.

create table if not exists public.glm_hub_authorized_users (
  id uuid primary key default gen_random_uuid(),
  email text not null unique,
  full_name text,
  role text not null default 'member',
  is_active boolean not null default true,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),

  constraint glm_hub_authorized_users_role_check
    check (role in ('admin', 'member')),

  constraint glm_hub_authorized_users_email_normalized_check
    check (email = lower(btrim(email))),

  constraint glm_hub_authorized_users_domain_check
    check (email ~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@gomezleemarketing\.com$'),

  constraint glm_hub_authorized_users_admin_check
    check (
      role <> 'admin'
      or email in (
        'jgomez@gomezleemarketing.com',
        'iaracena@gomezleemarketing.com',
        'ethen@gomezleemarketing.com',
        'mgomez@gomezleemarketing.com',
        'lmatos@gomezleemarketing.com'
      )
    )
);

comment on table public.glm_hub_authorized_users is
  'Excepciones de acceso del GLM Hub: administradores activos y usuarios bloqueados. Los usuarios corporativos normales no requieren registro.';

create or replace function public.glm_hub_normalize_authorized_user()
returns trigger
language plpgsql
set search_path = public
as $$
begin
  new.email := lower(btrim(new.email));
  new.full_name := nullif(btrim(new.full_name), '');
  new.updated_at := now();
  return new;
end;
$$;

drop trigger if exists glm_hub_normalize_authorized_user_trigger
  on public.glm_hub_authorized_users;

create trigger glm_hub_normalize_authorized_user_trigger
before insert or update on public.glm_hub_authorized_users
for each row
execute function public.glm_hub_normalize_authorized_user();

alter table public.glm_hub_authorized_users enable row level security;
alter table public.glm_hub_authorized_users force row level security;

revoke all on table public.glm_hub_authorized_users from anon;
revoke all on table public.glm_hub_authorized_users from authenticated;
grant select on table public.glm_hub_authorized_users to authenticated;
grant all on table public.glm_hub_authorized_users to service_role;

drop policy if exists "GLM Hub users can read only their active access"
  on public.glm_hub_authorized_users;
drop policy if exists "GLM Hub users can read their own control record"
  on public.glm_hub_authorized_users;

-- Cada persona solo puede consultar su propia excepción. Esto permite que el
-- frontend detecte también un bloqueo (is_active = false) sin exponer a otros.
create policy "GLM Hub users can read their own control record"
on public.glm_hub_authorized_users
for select
to authenticated
using (
  email = lower(coalesce((select auth.jwt() ->> 'email'), ''))
);

insert into public.glm_hub_authorized_users (email, full_name, role, is_active)
values
  ('jgomez@gomezleemarketing.com', 'José Leopoldo Gómez', 'admin', true),
  ('iaracena@gomezleemarketing.com', 'Isaac Aracena', 'admin', true),
  ('ethen@gomezleemarketing.com', 'Eidan Then', 'admin', true),
  ('mgomez@gomezleemarketing.com', 'Máximo Gomez', 'admin', true),
  ('lmatos@gomezleemarketing.com', 'Luis Matos', 'admin', true)
on conflict (email) do update
set
  full_name = excluded.full_name,
  role = excluded.role,
  is_active = true,
  updated_at = now();

-- Los registros member activos ya no son necesarios: la ausencia de fila
-- significa Usuario normal. Se conservan únicamente admins y bloqueos.
delete from public.glm_hub_authorized_users
where role = 'member'
  and is_active = true;

create or replace function public.glm_hub_is_active_user()
returns boolean
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
  v_email text;
begin
  v_email := lower(coalesce(auth.jwt() ->> 'email', ''));

  return
    auth.uid() is not null
    and v_email ~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@gomezleemarketing\.com$'
    and not exists (
      select 1
      from public.glm_hub_authorized_users as access
      where access.email = v_email
        and access.is_active = false
    );
end;
$$;

create or replace function public.glm_hub_is_admin()
returns boolean
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
  v_email text;
begin
  v_email := lower(coalesce(auth.jwt() ->> 'email', ''));

  return
    auth.uid() is not null
    and exists (
      select 1
      from public.glm_hub_authorized_users as access
      where access.email = v_email
        and access.role = 'admin'
        and access.is_active = true
    );
end;
$$;

alter function public.glm_hub_is_active_user() owner to postgres;
alter function public.glm_hub_is_admin() owner to postgres;

-- n8n llama esta función usando el JWT recibido desde el frontend.
create or replace function public.glm_hub_icon_generation_access()
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
  v_email text;
  v_user_id text;
  v_authorized boolean;
begin
  v_email := lower(coalesce(auth.jwt() ->> 'email', ''));
  v_user_id := coalesce(auth.uid()::text, '');

  select exists (
    select 1
    from public.glm_hub_authorized_users as access
    where access.email = v_email
      and access.role = 'admin'
      and access.is_active = true
  )
  into v_authorized;

  return jsonb_build_object(
    'authorized', coalesce(v_authorized, false),
    'email', v_email,
    'userId', v_user_id
  );
end;
$$;

alter function public.glm_hub_icon_generation_access() owner to postgres;

revoke all on function public.glm_hub_is_active_user() from public, anon;
revoke all on function public.glm_hub_is_admin() from public, anon;
revoke all on function public.glm_hub_icon_generation_access() from public, anon;
grant execute on function public.glm_hub_is_active_user() to authenticated;
grant execute on function public.glm_hub_is_admin() to authenticated;
grant execute on function public.glm_hub_icon_generation_access() to authenticated;
grant execute on function public.glm_hub_is_active_user() to service_role;
grant execute on function public.glm_hub_is_admin() to service_role;
grant execute on function public.glm_hub_icon_generation_access() to service_role;

-- --------------------------------------------------------------------------
-- 2. CATÁLOGO COMPARTIDO
-- --------------------------------------------------------------------------

create table if not exists public.glm_hub_apps (
  id uuid primary key default gen_random_uuid(),
  name text not null,
  description text not null,
  category text not null,
  url text,
  icon_url text not null,
  visibility text not null default 'published',
  created_by uuid default auth.uid() references auth.users(id) on delete set null,
  updated_by uuid default auth.uid() references auth.users(id) on delete set null,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),

  constraint glm_hub_apps_name_length_check
    check (char_length(btrim(name)) between 2 and 60),
  constraint glm_hub_apps_description_length_check
    check (char_length(btrim(description)) between 1 and 180),
  constraint glm_hub_apps_category_check
    check (category in ('Administración', 'Recursos Humanos', 'CDC')),
  constraint glm_hub_apps_visibility_check
    check (visibility in ('published', 'hidden')),
  constraint glm_hub_apps_icon_url_check
    check (icon_url ~ '^https://')
);

create unique index if not exists glm_hub_apps_name_unique_ci
  on public.glm_hub_apps (lower(btrim(name)));

create index if not exists glm_hub_apps_visibility_category_name_idx
  on public.glm_hub_apps (visibility, category, name);

create or replace function public.glm_hub_normalize_app()
returns trigger
language plpgsql
set search_path = public
as $$
begin
  new.name := btrim(new.name);
  new.description := btrim(new.description);
  new.category := btrim(new.category);
  new.url := nullif(btrim(new.url), '');
  new.icon_url := btrim(new.icon_url);
  new.updated_at := now();
  return new;
end;
$$;

drop trigger if exists glm_hub_normalize_app_trigger on public.glm_hub_apps;
create trigger glm_hub_normalize_app_trigger
before insert or update on public.glm_hub_apps
for each row
execute function public.glm_hub_normalize_app();

alter table public.glm_hub_apps enable row level security;
alter table public.glm_hub_apps force row level security;
alter table public.glm_hub_apps replica identity full;

revoke all on table public.glm_hub_apps from anon;
revoke all on table public.glm_hub_apps from authenticated;
grant select, insert, update, delete on table public.glm_hub_apps to authenticated;
grant all on table public.glm_hub_apps to service_role;

drop policy if exists "GLM Hub active users read shared apps" on public.glm_hub_apps;
drop policy if exists "GLM Hub admins create apps" on public.glm_hub_apps;
drop policy if exists "GLM Hub admins update apps" on public.glm_hub_apps;
drop policy if exists "GLM Hub admins delete apps" on public.glm_hub_apps;

create policy "GLM Hub active users read shared apps"
on public.glm_hub_apps
for select
to authenticated
using (
  public.glm_hub_is_active_user()
  and (visibility = 'published' or public.glm_hub_is_admin())
);

create policy "GLM Hub admins create apps"
on public.glm_hub_apps
for insert
to authenticated
with check (
  public.glm_hub_is_admin()
  and created_by = (select auth.uid())
  and updated_by = (select auth.uid())
);

create policy "GLM Hub admins update apps"
on public.glm_hub_apps
for update
to authenticated
using (public.glm_hub_is_admin())
with check (
  public.glm_hub_is_admin()
  and updated_by = (select auth.uid())
);

create policy "GLM Hub admins delete apps"
on public.glm_hub_apps
for delete
to authenticated
using (public.glm_hub_is_admin());

-- --------------------------------------------------------------------------
-- 3. FAVORITOS POR USUARIO
-- --------------------------------------------------------------------------

create table if not exists public.glm_hub_favorites (
  user_id uuid not null references auth.users(id) on delete cascade,
  app_id uuid not null references public.glm_hub_apps(id) on delete cascade,
  created_at timestamptz not null default now(),
  primary key (user_id, app_id)
);

create index if not exists glm_hub_favorites_user_created_idx
  on public.glm_hub_favorites (user_id, created_at desc);

alter table public.glm_hub_favorites enable row level security;
alter table public.glm_hub_favorites force row level security;

revoke all on table public.glm_hub_favorites from anon;
revoke all on table public.glm_hub_favorites from authenticated;
grant select, insert, update, delete on table public.glm_hub_favorites to authenticated;
grant all on table public.glm_hub_favorites to service_role;

drop policy if exists "GLM Hub users read their favorites" on public.glm_hub_favorites;
drop policy if exists "GLM Hub users create their favorites" on public.glm_hub_favorites;
drop policy if exists "GLM Hub users update their favorites" on public.glm_hub_favorites;
drop policy if exists "GLM Hub users delete their favorites" on public.glm_hub_favorites;

create policy "GLM Hub users read their favorites"
on public.glm_hub_favorites
for select
to authenticated
using (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

create policy "GLM Hub users create their favorites"
on public.glm_hub_favorites
for insert
to authenticated
with check (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

create policy "GLM Hub users update their favorites"
on public.glm_hub_favorites
for update
to authenticated
using (public.glm_hub_is_active_user() and user_id = (select auth.uid()))
with check (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

create policy "GLM Hub users delete their favorites"
on public.glm_hub_favorites
for delete
to authenticated
using (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

-- --------------------------------------------------------------------------
-- 4. ACCESOS RECIENTES POR USUARIO
-- --------------------------------------------------------------------------

create table if not exists public.glm_hub_recent_apps (
  user_id uuid not null references auth.users(id) on delete cascade,
  app_id uuid not null references public.glm_hub_apps(id) on delete cascade,
  last_opened_at timestamptz not null default now(),
  primary key (user_id, app_id)
);

create index if not exists glm_hub_recent_apps_user_date_idx
  on public.glm_hub_recent_apps (user_id, last_opened_at desc);

alter table public.glm_hub_recent_apps enable row level security;
alter table public.glm_hub_recent_apps force row level security;

revoke all on table public.glm_hub_recent_apps from anon;
revoke all on table public.glm_hub_recent_apps from authenticated;
grant select, insert, update, delete on table public.glm_hub_recent_apps to authenticated;
grant all on table public.glm_hub_recent_apps to service_role;

drop policy if exists "GLM Hub users read their recent apps" on public.glm_hub_recent_apps;
drop policy if exists "GLM Hub users create their recent apps" on public.glm_hub_recent_apps;
drop policy if exists "GLM Hub users update their recent apps" on public.glm_hub_recent_apps;
drop policy if exists "GLM Hub users delete their recent apps" on public.glm_hub_recent_apps;

create policy "GLM Hub users read their recent apps"
on public.glm_hub_recent_apps
for select
to authenticated
using (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

create policy "GLM Hub users create their recent apps"
on public.glm_hub_recent_apps
for insert
to authenticated
with check (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

create policy "GLM Hub users update their recent apps"
on public.glm_hub_recent_apps
for update
to authenticated
using (public.glm_hub_is_active_user() and user_id = (select auth.uid()))
with check (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

create policy "GLM Hub users delete their recent apps"
on public.glm_hub_recent_apps
for delete
to authenticated
using (public.glm_hub_is_active_user() and user_id = (select auth.uid()));

-- --------------------------------------------------------------------------
-- 5. STORAGE PARA LOS ÍCONOS
-- --------------------------------------------------------------------------

insert into storage.buckets (
  id,
  name,
  public,
  file_size_limit,
  allowed_mime_types
)
values (
  'glm-hub-icons',
  'glm-hub-icons',
  true,
  1048576,
  array['image/png', 'image/jpeg', 'image/webp']::text[]
)
on conflict (id) do update
set
  name = excluded.name,
  public = true,
  file_size_limit = excluded.file_size_limit,
  allowed_mime_types = excluded.allowed_mime_types;

drop policy if exists "GLM Hub active users read icons" on storage.objects;
drop policy if exists "GLM Hub admins upload icons" on storage.objects;
drop policy if exists "GLM Hub admins update icons" on storage.objects;
drop policy if exists "GLM Hub admins delete icons" on storage.objects;

create policy "GLM Hub active users read icons"
on storage.objects
for select
to authenticated
using (
  bucket_id = 'glm-hub-icons'
  and public.glm_hub_is_active_user()
);

create policy "GLM Hub admins upload icons"
on storage.objects
for insert
to authenticated
with check (
  bucket_id = 'glm-hub-icons'
  and public.glm_hub_is_admin()
);

create policy "GLM Hub admins update icons"
on storage.objects
for update
to authenticated
using (
  bucket_id = 'glm-hub-icons'
  and public.glm_hub_is_admin()
)
with check (
  bucket_id = 'glm-hub-icons'
  and public.glm_hub_is_admin()
);

create policy "GLM Hub admins delete icons"
on storage.objects
for delete
to authenticated
using (
  bucket_id = 'glm-hub-icons'
  and public.glm_hub_is_admin()
);

-- --------------------------------------------------------------------------
-- 6. SOLICITUDES DE ACCESO A APLICACIONES
-- --------------------------------------------------------------------------

create table if not exists public.glm_hub_access_requests (
  id uuid primary key default gen_random_uuid(),
  requester_user_id uuid not null references auth.users(id) on delete cascade,
  requester_name text not null,
  requester_email text not null,
  app_id uuid references public.glm_hub_apps(id) on delete set null,
  app_name text not null,
  app_category text not null,
  status text not null default 'pending',
  decided_by_name text,
  decided_by_email text,
  decided_at timestamptz,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),

  constraint glm_hub_access_requests_status_check
    check (status in ('pending', 'approved', 'rejected')),
  constraint glm_hub_access_requests_requester_email_check
    check (requester_email = lower(btrim(requester_email))),
  constraint glm_hub_access_requests_category_check
    check (app_category in ('Administración', 'Recursos Humanos', 'CDC'))
);

create unique index if not exists glm_hub_access_requests_one_pending_idx
  on public.glm_hub_access_requests (requester_email, app_id)
  where status = 'pending' and app_id is not null;

create index if not exists glm_hub_access_requests_status_created_idx
  on public.glm_hub_access_requests (status, created_at desc);

create table if not exists public.glm_hub_access_request_approvers (
  id uuid primary key default gen_random_uuid(),
  request_id uuid not null references public.glm_hub_access_requests(id) on delete cascade,
  approver_name text not null,
  approver_email text not null,
  decision_token text not null unique,
  decision_token_hash text not null unique,
  clicked_at timestamptz,
  created_at timestamptz not null default now(),

  constraint glm_hub_access_request_approvers_email_check
    check (
      approver_email in (
        'iaracena@gomezleemarketing.com',
        'jgomez@gomezleemarketing.com',
        'mgomez@gomezleemarketing.com'
      )
    ),
  constraint glm_hub_access_request_approvers_unique_request_email
    unique (request_id, approver_email)
);

alter table public.glm_hub_access_requests enable row level security;
alter table public.glm_hub_access_requests force row level security;
alter table public.glm_hub_access_request_approvers enable row level security;
alter table public.glm_hub_access_request_approvers force row level security;

revoke all on table public.glm_hub_access_requests from anon, authenticated;
revoke all on table public.glm_hub_access_request_approvers from anon, authenticated;
grant all on table public.glm_hub_access_requests to service_role;
grant all on table public.glm_hub_access_request_approvers to service_role;

-- Los usuarios pueden consultar solamente sus propias solicitudes desde Supabase,
-- aunque el frontend actual no necesita leer esta tabla.
drop policy if exists "GLM Hub users read their access requests"
  on public.glm_hub_access_requests;

create policy "GLM Hub users read their access requests"
on public.glm_hub_access_requests
for select
to authenticated
using (
  requester_user_id = auth.uid()
  and public.glm_hub_is_active_user()
);

grant select on table public.glm_hub_access_requests to authenticated;

-- Registra la solicitud usando exclusivamente la identidad del JWT del usuario.
-- No devuelve enlaces de aprobación al navegador, evitando que el solicitante
-- pueda obtenerlos o aprobarse a sí mismo.
create or replace function public.glm_hub_prepare_access_request(p_app_id uuid)
returns jsonb
language plpgsql
volatile
security definer
set search_path = ''
as $$
declare
  v_user_id uuid;
  v_email text;
  v_name text;
  v_control_name text;
  v_role text;
  v_is_active boolean;
  v_app_name text;
  v_app_category text;
  v_request_id uuid;
begin
  v_user_id := auth.uid();
  v_email := lower(coalesce(auth.jwt() ->> 'email', ''));

  if v_user_id is null or v_email = '' then
    return jsonb_build_object('ok', false, 'error', 'Tu sesión no es válida. Cierra sesión e inicia nuevamente.');
  end if;

  if v_email !~ '^[a-z0-9.!#$%&''*+/=?^_`{|}~-]+@gomezleemarketing\.com$' then
    return jsonb_build_object('ok', false, 'error', 'Debes utilizar una cuenta corporativa de GomezLee Marketing.');
  end if;

  select access.full_name, access.role, access.is_active
    into v_control_name, v_role, v_is_active
  from public.glm_hub_authorized_users as access
  where access.email = v_email
  limit 1;

  if found and v_is_active = false then
    return jsonb_build_object('ok', false, 'error', 'Tu acceso al GLM Hub está deshabilitado. Contacta a IT Support.');
  end if;

  if coalesce(v_role, 'member') = 'admin' then
    return jsonb_build_object('ok', false, 'error', 'Los administradores no necesitan solicitar acceso desde el Hub.');
  end if;

  v_name := coalesce(
    nullif(btrim(v_control_name), ''),
    nullif(btrim(auth.jwt() -> 'user_metadata' ->> 'full_name'), ''),
    nullif(btrim(auth.jwt() -> 'user_metadata' ->> 'name'), ''),
    split_part(v_email, '@', 1)
  );

  select app.name, app.category
    into v_app_name, v_app_category
  from public.glm_hub_apps as app
  where app.id = p_app_id
    and app.visibility = 'published'
  limit 1;

  if not found then
    return jsonb_build_object('ok', false, 'error', 'La aplicación seleccionada ya no está publicada.');
  end if;

  if exists (
    select 1
    from public.glm_hub_access_requests as request
    where request.requester_email = v_email
      and request.app_id = p_app_id
      and request.status = 'pending'
  ) then
    return jsonb_build_object(
      'ok', false,
      'code', 'PENDING_EXISTS',
      'error', 'Ya existe una solicitud pendiente para esta aplicación.'
    );
  end if;

  insert into public.glm_hub_access_requests (
    requester_user_id,
    requester_name,
    requester_email,
    app_id,
    app_name,
    app_category
  )
  values (
    v_user_id,
    v_name,
    v_email,
    p_app_id,
    v_app_name,
    v_app_category
  )
  returning id into v_request_id;

  return jsonb_build_object(
    'ok', true,
    'requestId', v_request_id,
    'requesterName', v_name,
    'requesterEmail', v_email,
    'appName', v_app_name,
    'appCategory', v_app_category
  );
end;
$$;

alter function public.glm_hub_prepare_access_request(uuid) owner to postgres;
revoke all on function public.glm_hub_prepare_access_request(uuid) from public, anon;
grant execute on function public.glm_hub_prepare_access_request(uuid) to authenticated;
grant execute on function public.glm_hub_prepare_access_request(uuid) to service_role;

-- Emite o recupera los tres enlaces de decisión. Solo n8n, usando la clave
-- service_role guardada en su nodo HTTP Request, puede ejecutar esta función.
create or replace function public.glm_hub_issue_access_request_tokens(p_request_id uuid)
returns jsonb
language plpgsql
volatile
security definer
set search_path = ''
as $$
declare
  v_request public.glm_hub_access_requests%rowtype;
  v_token_isaac text;
  v_token_jose text;
  v_token_maximo text;
begin
  select request.*
    into v_request
  from public.glm_hub_access_requests as request
  where request.id = p_request_id
    and request.status = 'pending'
  for update;

  if not found then
    return jsonb_build_object('ok', false, 'error', 'La solicitud no existe o ya fue atendida.');
  end if;

  if not exists (
    select 1
    from public.glm_hub_access_request_approvers as approver
    where approver.request_id = p_request_id
  ) then
    v_token_isaac := encode(extensions.gen_random_bytes(32), 'hex');
    v_token_jose := encode(extensions.gen_random_bytes(32), 'hex');
    v_token_maximo := encode(extensions.gen_random_bytes(32), 'hex');

    insert into public.glm_hub_access_request_approvers (
      request_id,
      approver_name,
      approver_email,
      decision_token,
      decision_token_hash
    )
    values
      (
        p_request_id,
        'Isaac Aracena',
        'iaracena@gomezleemarketing.com',
        v_token_isaac,
        encode(extensions.digest(v_token_isaac, 'sha256'), 'hex')
      ),
      (
        p_request_id,
        'José Leopoldo Gómez',
        'jgomez@gomezleemarketing.com',
        v_token_jose,
        encode(extensions.digest(v_token_jose, 'sha256'), 'hex')
      ),
      (
        p_request_id,
        'Máximo Gómez',
        'mgomez@gomezleemarketing.com',
        v_token_maximo,
        encode(extensions.digest(v_token_maximo, 'sha256'), 'hex')
      );
  end if;

  return jsonb_build_object(
    'ok', true,
    'requestId', v_request.id,
    'requesterName', v_request.requester_name,
    'requesterEmail', v_request.requester_email,
    'appName', v_request.app_name,
    'appCategory', v_request.app_category,
    'approvers', (
      select jsonb_agg(
        jsonb_build_object(
          'name', approver.approver_name,
          'email', approver.approver_email,
          'token', approver.decision_token
        )
        order by approver.approver_email
      )
      from public.glm_hub_access_request_approvers as approver
      where approver.request_id = p_request_id
    )
  );
end;
$$;

alter function public.glm_hub_issue_access_request_tokens(uuid) owner to postgres;
revoke all on function public.glm_hub_issue_access_request_tokens(uuid) from public, anon, authenticated;
grant execute on function public.glm_hub_issue_access_request_tokens(uuid) to service_role;

-- Registra una sola decisión de forma atómica. Aunque los botones sigan visibles
-- en correos ya entregados, cualquier segundo clic queda bloqueado en la base de datos.
create or replace function public.glm_hub_decide_access_request(
  p_token text,
  p_decision text
)
returns jsonb
language plpgsql
volatile
security definer
set search_path = ''
as $$
declare
  v_request_id uuid;
  v_approver_name text;
  v_approver_email text;
  v_request public.glm_hub_access_requests%rowtype;
  v_normalized_decision text;
begin
  v_normalized_decision := lower(btrim(coalesce(p_decision, '')));

  if v_normalized_decision not in ('approved', 'rejected') then
    return jsonb_build_object('ok', false, 'error', 'La decisión recibida no es válida.');
  end if;

  select approver.request_id, approver.approver_name, approver.approver_email
    into v_request_id, v_approver_name, v_approver_email
  from public.glm_hub_access_request_approvers as approver
  where approver.decision_token_hash = encode(
    extensions.digest(btrim(coalesce(p_token, '')), 'sha256'),
    'hex'
  )
  limit 1;

  if not found then
    return jsonb_build_object('ok', false, 'error', 'El enlace de decisión no es válido o ya no existe.');
  end if;

  update public.glm_hub_access_requests as request
  set
    status = v_normalized_decision,
    decided_by_name = v_approver_name,
    decided_by_email = v_approver_email,
    decided_at = now(),
    updated_at = now()
  where request.id = v_request_id
    and request.status = 'pending'
  returning request.* into v_request;

  if found then
    update public.glm_hub_access_request_approvers
    set clicked_at = now()
    where request_id = v_request_id
      and approver_email = v_approver_email;

    return jsonb_build_object(
      'ok', true,
      'alreadyDecided', false,
      'requestId', v_request.id,
      'status', v_request.status,
      'requesterName', v_request.requester_name,
      'requesterEmail', v_request.requester_email,
      'appName', v_request.app_name,
      'appCategory', v_request.app_category,
      'decidedByName', v_request.decided_by_name,
      'decidedByEmail', v_request.decided_by_email,
      'decidedAt', v_request.decided_at
    );
  end if;

  select request.*
    into v_request
  from public.glm_hub_access_requests as request
  where request.id = v_request_id;

  return jsonb_build_object(
    'ok', false,
    'alreadyDecided', true,
    'status', v_request.status,
    'requesterName', v_request.requester_name,
    'requesterEmail', v_request.requester_email,
    'appName', v_request.app_name,
    'decidedByName', coalesce(v_request.decided_by_name, ''),
    'decidedByEmail', coalesce(v_request.decided_by_email, ''),
    'decidedAt', v_request.decided_at,
    'error', 'Esta solicitud ya fue atendida y no admite otra decisión.'
  );
end;
$$;

alter function public.glm_hub_decide_access_request(text, text) owner to postgres;
revoke all on function public.glm_hub_decide_access_request(text, text) from public;
grant execute on function public.glm_hub_decide_access_request(text, text) to anon;
grant execute on function public.glm_hub_decide_access_request(text, text) to authenticated;
grant execute on function public.glm_hub_decide_access_request(text, text) to service_role;

-- --------------------------------------------------------------------------
-- 7. REALTIME DEL CATÁLOGO
-- --------------------------------------------------------------------------

-- Hace que una aplicación creada/editada/eliminada por un administrador
-- aparezca en las sesiones abiertas de los demás usuarios sin recargar.
do $$
begin
  if exists (
    select 1 from pg_publication where pubname = 'supabase_realtime'
  ) and not exists (
    select 1
    from pg_publication_tables
    where pubname = 'supabase_realtime'
      and schemaname = 'public'
      and tablename = 'glm_hub_apps'
  ) then
    execute 'alter publication supabase_realtime add table public.glm_hub_apps';
  end if;
end;
$$;

commit;

-- ============================================================================
-- OPERACIONES DE ADMINISTRACIÓN (EJECUTAR DESDE SQL EDITOR)
-- ============================================================================

-- IMPORTANTE:
-- Todo correo @gomezleemarketing.com entra automáticamente como Usuario.
-- La tabla glm_hub_authorized_users se usa solo para administradores y bloqueos.

-- CONVERTIR UN USUARIO EN ADMINISTRADOR:
-- insert into public.glm_hub_authorized_users (email, full_name, role, is_active)
-- values ('usuario@gomezleemarketing.com', 'Nombre del usuario', 'admin', true)
-- on conflict (email) do update
-- set full_name = excluded.full_name, role = 'admin', is_active = true, updated_at = now();

-- DEVOLVER UN ADMINISTRADOR A USUARIO NORMAL:
-- delete from public.glm_hub_authorized_users
-- where email = 'usuario@gomezleemarketing.com';

-- BLOQUEAR EXCEPCIONALMENTE A UN USUARIO DEL DOMINIO:
-- insert into public.glm_hub_authorized_users (email, full_name, role, is_active)
-- values ('usuario@gomezleemarketing.com', 'Nombre del usuario', 'member', false)
-- on conflict (email) do update
-- set full_name = excluded.full_name, role = 'member', is_active = false, updated_at = now();

-- DESBLOQUEAR A UN USUARIO NORMAL:
-- delete from public.glm_hub_authorized_users
-- where email = 'usuario@gomezleemarketing.com'
--   and role = 'member';

-- VER ADMINISTRADORES Y BLOQUEOS:
-- select email, full_name, role, is_active, updated_at
-- from public.glm_hub_authorized_users
-- order by role, email;

-- VER SOLICITUDES DE ACCESO:
-- select requester_name, requester_email, app_name, status, decided_by_name, decided_by_email, created_at, decided_at
-- from public.glm_hub_access_requests
-- order by created_at desc;

-- VER EL CATÁLOGO COMPARTIDO:
-- select id, name, category, visibility, url, icon_url, updated_at
-- from public.glm_hub_apps
-- order by name;
