with ranked_active_codes as (
  select id,
         row_number() over (
           partition by tenant_id, phone, purpose
           order by created_at desc, id desc
         ) as row_no
  from public.sms_verification_codes
  where consumed_at is null and status in ('pending', 'sent')
)
update public.sms_verification_codes code
set status = 'expired',
    metadata = code.metadata || '{"expiredBy":"sms-active-reservation-migration"}'::jsonb
from ranked_active_codes ranked
where code.id = ranked.id and ranked.row_no > 1;

create unique index if not exists idx_sms_codes_active_phone_reservation
  on public.sms_verification_codes (tenant_id, phone, purpose)
  where consumed_at is null and status in ('pending', 'sent');

create table if not exists app_private.sms_send_rate_limits (
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  dimension text not null check (dimension in ('tenant', 'phone', 'ip', 'device')),
  scope_hash text not null,
  bucket_start timestamptz not null,
  request_count integer not null default 0 check (request_count >= 0),
  updated_at timestamptz not null default now(),
  primary key (tenant_id, dimension, scope_hash, bucket_start)
);

revoke all on table app_private.sms_send_rate_limits from public, anon, authenticated;

alter table app_private.sms_send_rate_limits enable row level security;

drop policy if exists platform_admin_sms_send_rate_limits on app_private.sms_send_rate_limits;
create policy platform_admin_sms_send_rate_limits on app_private.sms_send_rate_limits
  for all
  using (app.is_platform_admin())
  with check (app.is_platform_admin());

create index if not exists idx_sms_send_rate_limits_updated
  on app_private.sms_send_rate_limits (updated_at);

comment on index public.idx_sms_codes_active_phone_reservation is
  'Atomically permits one active SMS send reservation per tenant, phone and purpose.';

comment on table app_private.sms_send_rate_limits is
  'Atomic hour/day SMS quota buckets. Dimension values are stored only as HMAC hashes.';

drop trigger if exists set_updated_at on app_private.sms_send_rate_limits;
create trigger set_updated_at
  before update on app_private.sms_send_rate_limits
  for each row execute function app.touch_updated_at();
