create or replace function app.uuid_array_from_jsonb(value jsonb)
returns uuid[]
language sql
immutable
as $$
  select coalesce(array_agg(item::uuid), '{}'::uuid[])
  from jsonb_array_elements_text(
    case when jsonb_typeof(value) = 'array' then value else '[]'::jsonb end
  ) as t(item)
  where item ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
$$;

create or replace function app.text_array_from_jsonb(value jsonb)
returns text[]
language sql
immutable
as $$
  select coalesce(array_agg(item), '{}'::text[])
  from jsonb_array_elements_text(
    case when jsonb_typeof(value) = 'array' then value else '[]'::jsonb end
  ) as t(item)
  where length(item) between 1 and 160
$$;

create or replace function app.public_question_bank_grant_allows(
  grant_allowed_region_ids uuid[],
  grant_allowed_subject_ids uuid[],
  source_region_id uuid,
  source_subject_ids uuid[]
)
returns boolean
language sql
immutable
as $$
  select
    (
      coalesce(array_length(grant_allowed_region_ids, 1), 0) = 0
      or source_region_id = any(grant_allowed_region_ids)
    )
    and (
      coalesce(array_length(grant_allowed_subject_ids, 1), 0) = 0
      or coalesce(array_length(source_subject_ids, 1), 0) = 0
      or source_subject_ids <@ grant_allowed_subject_ids
    )
$$;

create or replace function app.public_question_bank_subscription_allows(
  subscription_metadata jsonb,
  plan_feature_flags jsonb,
  source_question_bank_id uuid,
  source_region_id uuid,
  source_subject_ids uuid[]
)
returns boolean
language plpgsql
immutable
as $$
declare
  plan_cfg jsonb := coalesce(plan_feature_flags #> '{publicQuestionBanks}', '{}'::jsonb);
  subscription_cfg jsonb := coalesce(subscription_metadata #> '{publicQuestionBankAccess}', '{}'::jsonb);
  plan_region_ids uuid[] := app.uuid_array_from_jsonb(plan_cfg->'allowedRegionIds');
  subscription_region_ids uuid[] := app.uuid_array_from_jsonb(subscription_cfg->'allowedRegionIds');
  plan_subject_ids uuid[] := app.uuid_array_from_jsonb(plan_cfg->'allowedSubjectIds');
  subscription_subject_ids uuid[] := app.uuid_array_from_jsonb(subscription_cfg->'allowedSubjectIds');
  plan_bank_ids uuid[] := app.uuid_array_from_jsonb(plan_cfg->'allowedQuestionBankIds');
  subscription_bank_ids uuid[] := app.uuid_array_from_jsonb(subscription_cfg->'allowedQuestionBankIds');
  mode text := coalesce(subscription_cfg->>'mode', plan_cfg->>'mode', 'all');
  requires_region_allowlist boolean :=
    case coalesce(subscription_cfg->>'requiresRegionAllowlist', plan_cfg->>'requiresRegionAllowlist')
      when 'true' then true
      when 'false' then false
      else false
    end;
  allow_all_regions boolean := (
    case coalesce(subscription_cfg->>'allowAllRegions', plan_cfg->>'allowAllRegions')
      when 'true' then true
      when 'false' then false
      else false
    end
  ) or mode in ('all', 'all_regions', 'national', 'unlimited');
begin
  if coalesce(subscription_cfg->>'enabled', plan_cfg->>'enabled', 'true') = 'false' then
    return false;
  end if;

  if coalesce(array_length(plan_bank_ids, 1), 0) > 0 and not source_question_bank_id = any(plan_bank_ids) then
    return false;
  end if;
  if coalesce(array_length(subscription_bank_ids, 1), 0) > 0 and not source_question_bank_id = any(subscription_bank_ids) then
    return false;
  end if;

  if not allow_all_regions and source_region_id is not null then
    if requires_region_allowlist
       and coalesce(array_length(plan_region_ids, 1), 0) = 0
       and coalesce(array_length(subscription_region_ids, 1), 0) = 0 then
      return false;
    end if;
    if coalesce(array_length(plan_region_ids, 1), 0) > 0 and not source_region_id = any(plan_region_ids) then
      return false;
    end if;
    if coalesce(array_length(subscription_region_ids, 1), 0) > 0 and not source_region_id = any(subscription_region_ids) then
      return false;
    end if;
  end if;

  if coalesce(array_length(plan_subject_ids, 1), 0) > 0
     and coalesce(array_length(source_subject_ids, 1), 0) > 0
     and not source_subject_ids <@ plan_subject_ids then
    return false;
  end if;
  if coalesce(array_length(subscription_subject_ids, 1), 0) > 0
     and coalesce(array_length(source_subject_ids, 1), 0) > 0
     and not source_subject_ids <@ subscription_subject_ids then
    return false;
  end if;

  return true;
end;
$$;

create index if not exists idx_question_bank_grants_allowed_regions
  on public.question_bank_grants using gin(allowed_region_ids);

create index if not exists idx_question_bank_grants_allowed_subjects
  on public.question_bank_grants using gin(allowed_subject_ids);

update public.platform_saas_plans
set feature_flags = jsonb_set(
      feature_flags,
      '{publicQuestionBanks}',
      '{"enabled":true,"mode":"limited_regions","requiresRegionAllowlist":true,"maxRegionCount":1}'::jsonb,
      true
    ),
    updated_at = now()
where code = 'starter_yearly';

update public.platform_saas_plans
set feature_flags = jsonb_set(
      feature_flags,
      '{publicQuestionBanks}',
      '{"enabled":true,"mode":"national","allowAllRegions":true}'::jsonb,
      true
    ),
    updated_at = now()
where code = 'pro_yearly';

drop policy if exists question_bank_grants_eligible_tenant_read on public.question_bank_grants;
create policy question_bank_grants_eligible_tenant_read on public.question_bank_grants
  for select
  using (
    status = 'active'
    and (starts_at is null or starts_at <= now())
    and (expires_at is null or expires_at > now())
    and exists (
      select 1
      from public.question_banks qb
      left join lateral (
        select array_agg(distinct q.subject_id) filter (where q.subject_id is not null) as subject_ids
        from public.questions q
        where q.question_bank_id = qb.id
          and q.status = 'published'
      ) ss on true
      where qb.id = source_question_bank_id
        and qb.source_scope = 'platform'
        and qb.status = 'active'
        and app.public_question_bank_grant_allows(
          allowed_region_ids,
          allowed_subject_ids,
          qb.region_id,
          coalesce(ss.subject_ids, '{}'::uuid[])
        )
        and (
          (
            grant_scope = 'all_active_tenants'
            and exists (
              select 1
              from public.tenant_subscriptions ts
              left join public.platform_saas_plans p on p.code = ts.plan_code
              where ts.tenant_id = app.current_tenant_id()
                and ts.status in ('trial', 'active')
                and (ts.expires_at is null or ts.expires_at > now())
                and app.public_question_bank_subscription_allows(
                  ts.metadata,
                  coalesce(p.feature_flags, '{}'::jsonb),
                  qb.id,
                  qb.region_id,
                  coalesce(ss.subject_ids, '{}'::uuid[])
                )
            )
          )
          or (
            grant_scope in ('plans', 'mixed')
            and exists (
              select 1
              from public.tenant_subscriptions ts
              left join public.platform_saas_plans p on p.code = ts.plan_code
              where ts.tenant_id = app.current_tenant_id()
                and ts.status in ('trial', 'active')
                and (ts.expires_at is null or ts.expires_at > now())
                and ts.plan_code = any(allowed_plan_codes)
                and app.public_question_bank_subscription_allows(
                  ts.metadata,
                  coalesce(p.feature_flags, '{}'::jsonb),
                  qb.id,
                  qb.region_id,
                  coalesce(ss.subject_ids, '{}'::uuid[])
                )
            )
          )
          or (
            grant_scope in ('tenants', 'mixed')
            and app.current_tenant_id() = any(allowed_tenant_ids)
            and (
              metadata->>'requiresActiveSubscription' = 'false'
              or exists (
                select 1
                from public.tenant_subscriptions ts
                left join public.platform_saas_plans p on p.code = ts.plan_code
                where ts.tenant_id = app.current_tenant_id()
                  and ts.status in ('trial', 'active')
                  and (ts.expires_at is null or ts.expires_at > now())
                  and app.public_question_bank_subscription_allows(
                    ts.metadata,
                    coalesce(p.feature_flags, '{}'::jsonb),
                    qb.id,
                    qb.region_id,
                    coalesce(ss.subject_ids, '{}'::uuid[])
                  )
              )
            )
          )
        )
    )
  );
