-- 72-exam-marker.sql
-- Exam-marker operational tables (Supabase = source of truth for geometry/marks/submissions).
-- Neo4j cc.public.exams holds the knowledge graph; joined by shared UUIDs (see spec §2).
--
-- Authorization is owned by this layer: the exam API calls Supabase AS THE USER, so these RLS
-- policies are enforced (service_role policies cover the Neo4j-projection / seed paths only).
-- Depends on: 71-class-management.sql (marking_batches.class_id → classes; user_institute_ids()).

--==========================================================================================
-- 1. Tables
--==========================================================================================

create table if not exists public.exam_templates (
    id uuid primary key default gen_random_uuid(),
    exam_id uuid references public.eb_exams(id) on delete set null,      -- null for ad-hoc upload
    exam_code text,                                                      -- denormalised → Neo4j join
    institute_id uuid not null references public.institutes(id) on delete cascade,
    teacher_id uuid not null references public.profiles(id) on delete cascade,
    title text not null,
    subject text,
    source_file_id uuid references public.files(id) on delete set null, -- uploaded PDF (R2.2)
    page_count int,
    status text not null default 'draft' check (status in ('draft','ready','archived')),
    created_at timestamptz not null default timezone('utc', now()),
    updated_at timestamptz not null default timezone('utc', now())
);

create table if not exists public.exam_questions (
    id uuid primary key default gen_random_uuid(),
    template_id uuid not null references public.exam_templates(id) on delete cascade,
    parent_id uuid references public.exam_questions(id) on delete cascade,
    label text not null,
    "order" int not null default 0,
    max_marks numeric not null default 0,
    answer_type text check (answer_type in ('written','mcq','short','diagram')),
    mcq_options jsonb,
    mark_scheme jsonb not null default '{}'::jsonb,    -- MarkScheme union from exam-marker types.ts
    is_container boolean not null default false,       -- true → Neo4j Question, false → Part
    spec_ref text,                                     -- manual spec-point tag → ASSESSES (R3.5.3)
    created_at timestamptz not null default timezone('utc', now()),
    updated_at timestamptz not null default timezone('utc', now())
);

create table if not exists public.exam_response_areas (
    id uuid primary key default gen_random_uuid(),     -- == Neo4j Region.uuid_string
    question_id uuid not null references public.exam_questions(id) on delete cascade,
    template_id uuid not null references public.exam_templates(id) on delete cascade,  -- RLS denorm
    page int not null,
    bounds jsonb not null,                             -- {x,y,w,h}
    kind text not null check (kind in ('response','context')),
    response_form text check (response_form in
        ('lines','answer-box','working','diagram','tick-boxes','table','blanks')),
    source text not null default 'manual' check (source in ('manual','ai')),
    confirmed boolean not null default true,
    confidence numeric,
    created_at timestamptz not null default timezone('utc', now())
);

create table if not exists public.exam_boundaries (
    id uuid primary key default gen_random_uuid(),
    template_id uuid not null references public.exam_templates(id) on delete cascade,
    question_id uuid references public.exam_questions(id) on delete set null,
    label text,
    page_index int not null,
    y numeric not null,
    bounds jsonb,
    source text not null default 'manual' check (source in ('manual','ai')),
    confirmed boolean not null default true,
    created_at timestamptz not null default timezone('utc', now())
);

create table if not exists public.marking_batches (
    id uuid primary key default gen_random_uuid(),
    template_id uuid not null references public.exam_templates(id) on delete cascade,
    class_id uuid references public.classes(id) on delete set null,     -- roster via class_students
    institute_id uuid not null references public.institutes(id) on delete cascade,
    teacher_id uuid not null references public.profiles(id) on delete cascade,   -- batch owner (R2.4)
    title text,
    status text not null default 'open' check (status in ('open','marking','complete','archived')),
    created_at timestamptz not null default timezone('utc', now()),
    updated_at timestamptz not null default timezone('utc', now())
);

create table if not exists public.student_submissions (
    id uuid primary key default gen_random_uuid(),
    batch_id uuid not null references public.marking_batches(id) on delete cascade,
    student_id uuid references public.profiles(id) on delete set null,  -- null until matched
    student_name text,
    scan_file_id uuid references public.files(id) on delete set null,
    scan_url text,
    qr_code text,
    matching_method text check (matching_method in ('ordered','ocr_name','qr_code','manual')),
    match_confidence numeric,
    page_start int,
    page_count int,
    status text not null default 'unmatched'
        check (status in ('unmatched','matched','marking','complete','absent')),
    annotation_snapshot jsonb,
    created_at timestamptz not null default timezone('utc', now()),
    updated_at timestamptz not null default timezone('utc', now())
);

create table if not exists public.mark_entries (
    id uuid primary key default gen_random_uuid(),
    submission_id uuid not null references public.student_submissions(id) on delete cascade,
    question_id uuid not null references public.exam_questions(id) on delete cascade,
    batch_id uuid not null references public.marking_batches(id) on delete cascade,  -- RLS denorm
    awarded_marks numeric not null default 0,
    mark_scheme_detail jsonb not null default '{}'::jsonb,
    annotation_shape_ids jsonb not null default '[]'::jsonb,
    comment text,
    marked_by text not null default 'teacher' check (marked_by in ('teacher','ai')),
    ai_confidence numeric,
    confirmed boolean not null default true,
    marked_at timestamptz not null default timezone('utc', now())
);

create index if not exists idx_exam_templates_institute on public.exam_templates(institute_id);
create index if not exists idx_exam_templates_teacher    on public.exam_templates(teacher_id);
create index if not exists idx_exam_questions_template    on public.exam_questions(template_id);
create index if not exists idx_exam_questions_parent      on public.exam_questions(parent_id);
create index if not exists idx_exam_regions_question      on public.exam_response_areas(question_id);
create index if not exists idx_exam_regions_template      on public.exam_response_areas(template_id);
create index if not exists idx_exam_boundaries_template   on public.exam_boundaries(template_id);
create index if not exists idx_batches_template           on public.marking_batches(template_id);
create index if not exists idx_batches_institute          on public.marking_batches(institute_id);
create index if not exists idx_submissions_batch          on public.student_submissions(batch_id);
create index if not exists idx_marks_submission           on public.mark_entries(submission_id);
create index if not exists idx_marks_batch                on public.mark_entries(batch_id);

--==========================================================================================
-- 2. updated_at triggers (reuse public.handle_updated_at from 62-functions-triggers.sql)
--==========================================================================================

drop trigger if exists handle_exam_templates_updated_at on public.exam_templates;
create trigger handle_exam_templates_updated_at before update on public.exam_templates
  for each row execute function public.handle_updated_at();

drop trigger if exists handle_exam_questions_updated_at on public.exam_questions;
create trigger handle_exam_questions_updated_at before update on public.exam_questions
  for each row execute function public.handle_updated_at();

drop trigger if exists handle_marking_batches_updated_at on public.marking_batches;
create trigger handle_marking_batches_updated_at before update on public.marking_batches
  for each row execute function public.handle_updated_at();

drop trigger if exists handle_student_submissions_updated_at on public.student_submissions;
create trigger handle_student_submissions_updated_at before update on public.student_submissions
  for each row execute function public.handle_updated_at();

--==========================================================================================
-- 3. RLS — every table: a service_role passthrough (Neo4j projection / seeds) + as-user policies
--==========================================================================================

alter table public.exam_templates       enable row level security;
alter table public.exam_questions        enable row level security;
alter table public.exam_response_areas   enable row level security;
alter table public.exam_boundaries       enable row level security;
alter table public.marking_batches       enable row level security;
alter table public.student_submissions   enable row level security;
alter table public.mark_entries          enable row level security;

-- exam_templates -------------------------------------------------------------------------
drop policy if exists exam_templates_service on public.exam_templates;
create policy exam_templates_service on public.exam_templates using (auth.role() = 'service_role');
drop policy if exists exam_templates_read on public.exam_templates;
create policy exam_templates_read on public.exam_templates for select to authenticated
  using (institute_id in (select public.user_institute_ids()));
drop policy if exists exam_templates_write on public.exam_templates;
create policy exam_templates_write on public.exam_templates for all to authenticated
  using (teacher_id = auth.uid() and institute_id in (select public.user_institute_ids()))
  with check (teacher_id = auth.uid() and institute_id in (select public.user_institute_ids()));

-- exam_questions / exam_response_areas / exam_boundaries: cascade authz from owning template
drop policy if exists exam_questions_service on public.exam_questions;
create policy exam_questions_service on public.exam_questions using (auth.role() = 'service_role');
drop policy if exists exam_questions_all on public.exam_questions;
create policy exam_questions_all on public.exam_questions for all to authenticated
  using (exists (select 1 from public.exam_templates t
                 where t.id = exam_questions.template_id
                   and t.institute_id in (select public.user_institute_ids())))
  with check (exists (select 1 from public.exam_templates t
                 where t.id = exam_questions.template_id and t.teacher_id = auth.uid()));

drop policy if exists exam_regions_service on public.exam_response_areas;
create policy exam_regions_service on public.exam_response_areas using (auth.role() = 'service_role');
drop policy if exists exam_regions_all on public.exam_response_areas;
create policy exam_regions_all on public.exam_response_areas for all to authenticated
  using (exists (select 1 from public.exam_templates t
                 where t.id = exam_response_areas.template_id
                   and t.institute_id in (select public.user_institute_ids())))
  with check (exists (select 1 from public.exam_templates t
                 where t.id = exam_response_areas.template_id and t.teacher_id = auth.uid()));

drop policy if exists exam_boundaries_service on public.exam_boundaries;
create policy exam_boundaries_service on public.exam_boundaries using (auth.role() = 'service_role');
drop policy if exists exam_boundaries_all on public.exam_boundaries;
create policy exam_boundaries_all on public.exam_boundaries for all to authenticated
  using (exists (select 1 from public.exam_templates t
                 where t.id = exam_boundaries.template_id
                   and t.institute_id in (select public.user_institute_ids())))
  with check (exists (select 1 from public.exam_templates t
                 where t.id = exam_boundaries.template_id and t.teacher_id = auth.uid()));

-- marking_batches: read = same institute (colleagues), write = owning teacher (R2.4)
drop policy if exists marking_batches_service on public.marking_batches;
create policy marking_batches_service on public.marking_batches using (auth.role() = 'service_role');
drop policy if exists marking_batches_read on public.marking_batches;
create policy marking_batches_read on public.marking_batches for select to authenticated
  using (institute_id in (select public.user_institute_ids()));
drop policy if exists marking_batches_write on public.marking_batches;
create policy marking_batches_write on public.marking_batches for all to authenticated
  using (teacher_id = auth.uid() and institute_id in (select public.user_institute_ids()))
  with check (teacher_id = auth.uid() and institute_id in (select public.user_institute_ids()));

-- student_submissions: authz cascades from batch ownership
drop policy if exists submissions_service on public.student_submissions;
create policy submissions_service on public.student_submissions using (auth.role() = 'service_role');
drop policy if exists submissions_all on public.student_submissions;
create policy submissions_all on public.student_submissions for all to authenticated
  using (exists (select 1 from public.marking_batches b
                 where b.id = student_submissions.batch_id and b.teacher_id = auth.uid()))
  with check (exists (select 1 from public.marking_batches b
                 where b.id = student_submissions.batch_id and b.teacher_id = auth.uid()));

-- mark_entries: teacher (batch owner) full; student may read their own marks (R1.5, UI deferred)
drop policy if exists marks_service on public.mark_entries;
create policy marks_service on public.mark_entries using (auth.role() = 'service_role');
drop policy if exists marks_teacher_all on public.mark_entries;
create policy marks_teacher_all on public.mark_entries for all to authenticated
  using (exists (select 1 from public.marking_batches b
                 where b.id = mark_entries.batch_id and b.teacher_id = auth.uid()))
  with check (exists (select 1 from public.marking_batches b
                 where b.id = mark_entries.batch_id and b.teacher_id = auth.uid()));
drop policy if exists marks_student_read on public.mark_entries;
create policy marks_student_read on public.mark_entries for select to authenticated
  using (exists (select 1 from public.student_submissions s
                 where s.id = mark_entries.submission_id and s.student_id = auth.uid()));
