alter table public.content_nodes
  add column if not exists access_rules jsonb not null default '{}'::jsonb;

alter table public.question_collections
  add column if not exists access_rules jsonb not null default '{}'::jsonb;

alter table public.practice_blueprints
  add column if not exists access_rules jsonb not null default '{}'::jsonb;

alter table public.practice_sessions
  add column if not exists access_mode text not null default 'free',
  add column if not exists access_entitlement_id uuid references public.entitlements(id) on delete set null,
  add column if not exists consumed_free_quota integer not null default 0,
  add column if not exists access_snapshot jsonb not null default '{}'::jsonb;

do $$
begin
  if not exists (select 1 from pg_constraint where conname = 'practice_sessions_access_mode_check') then
    alter table public.practice_sessions
      add constraint practice_sessions_access_mode_check
      check (access_mode in ('free', 'svip', 'staff'));
  end if;

  if not exists (select 1 from pg_constraint where conname = 'practice_sessions_consumed_free_quota_check') then
    alter table public.practice_sessions
      add constraint practice_sessions_consumed_free_quota_check
      check (consumed_free_quota >= 0);
  end if;
end $$;

create table if not exists public.practice_daily_usage (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  user_id uuid not null references public.platform_users(id) on delete cascade,
  usage_date date not null default current_date,
  scope_type text not null default 'tenant'
    check (scope_type in ('tenant', 'region', 'subject', 'question_bank', 'content_entry', 'content_node', 'collection', 'blueprint')),
  scope_id uuid,
  free_limit integer not null default 25 check (free_limit >= 0),
  used_count integer not null default 0 check (used_count >= 0),
  metadata jsonb not null default '{}'::jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create table if not exists public.practice_access_events (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  user_id uuid references public.platform_users(id) on delete set null,
  practice_session_id uuid references public.practice_sessions(id) on delete set null,
  event_type text not null
    check (event_type in ('session_created', 'session_denied', 'quota_consumed')),
  access_mode text not null default 'free'
    check (access_mode in ('free', 'svip', 'staff', 'denied')),
  requested_count integer not null default 0 check (requested_count >= 0),
  granted_count integer not null default 0 check (granted_count >= 0),
  consumed_free_quota integer not null default 0 check (consumed_free_quota >= 0),
  reason text,
  scope_type text,
  scope_id uuid,
  entitlement_id uuid references public.entitlements(id) on delete set null,
  metadata jsonb not null default '{}'::jsonb,
  created_at timestamptz not null default now()
);

create index if not exists idx_practice_daily_usage_user
  on public.practice_daily_usage(tenant_id, user_id, usage_date desc);

create unique index if not exists idx_practice_daily_usage_unique_scope
  on public.practice_daily_usage(tenant_id, user_id, usage_date, scope_type, scope_id)
  nulls not distinct;

create index if not exists idx_practice_access_events_user
  on public.practice_access_events(tenant_id, user_id, created_at desc);

create index if not exists idx_practice_access_events_session
  on public.practice_access_events(tenant_id, practice_session_id);

do $$
declare
  table_name text;
begin
  foreach table_name in array array['practice_daily_usage', 'practice_access_events']
  loop
    execute format('alter table public.%I enable row level security', table_name);
    execute format('drop policy if exists tenant_isolation on public.%I', table_name);
    execute format(
      'create policy tenant_isolation on public.%I for all using (tenant_id = app.current_tenant_id() or app.is_platform_admin()) with check (tenant_id = app.current_tenant_id() or app.is_platform_admin())',
      table_name
    );
    execute format('drop trigger if exists set_updated_at on public.%I', table_name);
  end loop;

  execute 'create trigger set_updated_at before update on public.practice_daily_usage for each row execute function app.touch_updated_at()';
end $$;
