-- 71-class-management.sql
-- Foundational: capture the (previously untracked) class-management schema and harden its RLS.
--
-- Background: `classes`, `class_teachers`, `class_students`, `enrollment_requests` existed only on
-- live dev (.94), created out-of-band, with NO tracked DDL. Their schema/FKs/uniques are sound,
-- but RLS exposed `class_students` / `class_teachers` to service_role ONLY — so any API path that
-- calls Supabase AS THE USER (the correct, RLS-enforced pattern) reads ZERO rows. This migration:
--   1. captures the real schema (idempotent; no-op on environments that already have it),
--   2. adds SECURITY DEFINER membership helpers (avoid RLS recursion in policies),
--   3. adds as-user RLS policies so teachers/admins/students can read rosters under RLS,
--      while keeping the existing service_role policies intact.
-- Verified against live .94 schema 2026-06-06 (all FKs/uniques/checks already present there).

--==========================================================================================
-- 1. Tables (idempotent capture for fresh environments; skipped where they already exist)
--==========================================================================================

create table if not exists public.classes (
    id uuid primary key default gen_random_uuid(),
    institute_id uuid not null references public.institutes(id) on delete cascade,
    name varchar not null,
    class_code text,
    subject varchar,
    key_stage text,
    year_group varchar,
    academic_year varchar,
    description text,
    type varchar not null default 'standard',
    is_active boolean not null default true,
    created_by uuid not null references public.profiles(id),
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now()
);

create table if not exists public.class_teachers (
    id uuid primary key default gen_random_uuid(),
    class_id uuid not null references public.classes(id) on delete cascade,
    teacher_id uuid not null references public.profiles(id) on delete cascade,
    is_primary boolean not null default false,
    can_edit boolean not null default true,
    assigned_at timestamptz not null default now(),
    assigned_by uuid references public.profiles(id),
    constraint class_teachers_class_id_teacher_id_key unique (class_id, teacher_id)
);

create table if not exists public.class_students (
    id uuid primary key default gen_random_uuid(),
    class_id uuid not null references public.classes(id) on delete cascade,
    student_id uuid not null references public.profiles(id) on delete cascade,
    status varchar not null default 'active'
        check (status::text = any (array['active','inactive','pending'])),
    enrolled_at timestamptz not null default now(),
    enrolled_by uuid references public.profiles(id),
    constraint class_students_class_id_student_id_key unique (class_id, student_id)
);

create table if not exists public.enrollment_requests (
    id uuid primary key default gen_random_uuid(),
    class_id uuid not null references public.classes(id) on delete cascade,
    student_id uuid not null references public.profiles(id) on delete cascade,
    status varchar not null default 'pending'
        check (status::text = any (array['pending','approved','rejected'])),
    request_message text,
    requested_at timestamptz not null default now(),
    responded_at timestamptz,
    responded_by uuid references public.profiles(id),
    response_message text
);

create index if not exists idx_classes_institute     on public.classes(institute_id);
create index if not exists idx_classes_created_by     on public.classes(created_by);
create index if not exists idx_classes_class_code     on public.classes(class_code);
create index if not exists idx_class_teachers_class    on public.class_teachers(class_id);
create index if not exists idx_class_students_class    on public.class_students(class_id);

--==========================================================================================
-- 2. SECURITY DEFINER membership helpers
--    Run as owner (bypass RLS on the inner tables) → no policy recursion when referenced below.
--==========================================================================================

create or replace function public.user_institute_ids()
returns setof uuid language sql stable security definer set search_path = public as $$
  select institute_id from public.institute_memberships where profile_id = auth.uid()
$$;

create or replace function public.is_class_teacher(p_class uuid)
returns boolean language sql stable security definer set search_path = public as $$
  select exists (select 1 from public.class_teachers
                 where class_id = p_class and teacher_id = auth.uid())
$$;

create or replace function public.is_class_admin(p_class uuid)
returns boolean language sql stable security definer set search_path = public as $$
  select exists (
    select 1 from public.classes c
    join public.institute_memberships m on m.institute_id = c.institute_id
    where c.id = p_class and m.profile_id = auth.uid()
      and m.role in ('school_admin','department_head'))
$$;

create or replace function public.is_institute_member_of_class(p_class uuid)
returns boolean language sql stable security definer set search_path = public as $$
  select exists (
    select 1 from public.classes c
    join public.institute_memberships m on m.institute_id = c.institute_id
    where c.id = p_class and m.profile_id = auth.uid())
$$;

--==========================================================================================
-- 3. RLS — enable + (re)declare every policy so this file is the source of truth.
--    Existing service_role / institute_read / er_own policies are preserved verbatim;
--    the cs_read/cs_write/ct_read/ct_write policies are the NEW as-user grants.
--==========================================================================================

alter table public.classes             enable row level security;
alter table public.class_teachers      enable row level security;
alter table public.class_students      enable row level security;
alter table public.enrollment_requests enable row level security;

-- classes ---------------------------------------------------------------------------------
drop policy if exists classes_service_role on public.classes;
create policy classes_service_role on public.classes
  using (auth.role() = 'service_role');

drop policy if exists classes_institute_read on public.classes;
create policy classes_institute_read on public.classes for select to authenticated
  using (institute_id in (select public.user_institute_ids()));

-- NEW: teachers/admins of a class can update it; admins can insert/delete within their institute
drop policy if exists classes_admin_write on public.classes;
create policy classes_admin_write on public.classes for all to authenticated
  using (institute_id in (select public.user_institute_ids())
         and (public.is_class_admin(id) or public.is_class_teacher(id)))
  with check (institute_id in (select public.user_institute_ids()));

-- class_teachers --------------------------------------------------------------------------
drop policy if exists ct_service_role on public.class_teachers;
create policy ct_service_role on public.class_teachers
  using (auth.role() = 'service_role');

-- NEW: institute members can see who teaches a class; the teacher can see their own rows
drop policy if exists ct_read on public.class_teachers;
create policy ct_read on public.class_teachers for select to authenticated
  using (teacher_id = auth.uid() or public.is_institute_member_of_class(class_id));

-- NEW: only school admins assign/unassign teachers
drop policy if exists ct_write on public.class_teachers;
create policy ct_write on public.class_teachers for all to authenticated
  using (public.is_class_admin(class_id))
  with check (public.is_class_admin(class_id));

-- class_students --------------------------------------------------------------------------
drop policy if exists cs_service_role on public.class_students;
create policy cs_service_role on public.class_students
  using (auth.role() = 'service_role');

-- NEW: a student sees their own enrolment; teachers/admins of the class see the roster
drop policy if exists cs_read on public.class_students;
create policy cs_read on public.class_students for select to authenticated
  using (student_id = auth.uid()
         or public.is_class_teacher(class_id)
         or public.is_class_admin(class_id));

-- NEW: teachers (can_edit) and admins of the class manage enrolments
drop policy if exists cs_write on public.class_students;
create policy cs_write on public.class_students for all to authenticated
  using (public.is_class_teacher(class_id) or public.is_class_admin(class_id))
  with check (public.is_class_teacher(class_id) or public.is_class_admin(class_id));

-- enrollment_requests ---------------------------------------------------------------------
drop policy if exists er_service_role on public.enrollment_requests;
create policy er_service_role on public.enrollment_requests
  using (auth.role() = 'service_role');

drop policy if exists er_own on public.enrollment_requests;
create policy er_own on public.enrollment_requests for all to authenticated
  using (student_id = auth.uid())
  with check (student_id = auth.uid());

-- NEW: teachers/admins of the class can read + respond to requests for their class
drop policy if exists er_class_staff on public.enrollment_requests;
create policy er_class_staff on public.enrollment_requests for all to authenticated
  using (public.is_class_teacher(class_id) or public.is_class_admin(class_id))
  with check (public.is_class_teacher(class_id) or public.is_class_admin(class_id));
