-- ============================================================
-- Classroom Copilot — Extended Schema
-- Migration 004: academic_term_breaks, academic_periods,
--               taught_lessons, invitations + ALTER extensions
-- Run after: 003_academic_calendar.sql
-- ============================================================

-- ─── admin_profiles: add updated_at trigger (table already exists) ───────────
DO $$ BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_trigger
        WHERE tgname = 'trg_updated_at'
          AND tgrelid = 'public.admin_profiles'::regclass
    ) THEN
        EXECUTE 'CREATE TRIGGER trg_updated_at
                 BEFORE UPDATE ON admin_profiles
                 FOR EACH ROW EXECUTE FUNCTION set_updated_at()';
    END IF;
END $$;

-- ─── 1. academic_term_breaks ─────────────────────────────────────────────────
-- Explicit named holiday periods between terms.
-- Admins name and date these; agents can look them up or even populate them.

CREATE TABLE IF NOT EXISTS academic_term_breaks (
    id                  UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    school_timetable_id UUID NOT NULL REFERENCES school_timetables(id) ON DELETE CASCADE,
    institute_id        UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    break_name          TEXT NOT NULL,       -- e.g. "Christmas Break", "Easter Break"
    start_date          DATE NOT NULL,
    end_date            DATE NOT NULL,
    notes               TEXT,
    tags                TEXT[] NOT NULL DEFAULT '{}',
    neo4j_node_id       TEXT,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (school_timetable_id, break_name)
);

-- ─── 2. academic_periods ─────────────────────────────────────────────────────
-- One row per period per ACADEMIC day (not holiday/staff days).
-- Instantiated at timetable setup time from school_timetables.periods_template.
-- Enables per-period notes, room assignments, and substitutions.

CREATE TABLE IF NOT EXISTS academic_periods (
    id               UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    academic_day_id  UUID NOT NULL REFERENCES academic_days(id) ON DELETE CASCADE,
    institute_id     UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    period_code      TEXT NOT NULL,      -- e.g. "1", "2", "Reg", "Break1"
    period_name      TEXT NOT NULL,      -- e.g. "Period 1", "Registration"
    period_type      TEXT NOT NULL CHECK (period_type IN ('lesson','break','registration','offtimetable')),
    start_time       TIME NOT NULL,
    end_time         TIME NOT NULL,
    room_code        TEXT,               -- default room; overridden per taught_lesson
    notes            TEXT,
    tags             TEXT[] NOT NULL DEFAULT '{}',
    neo4j_node_id    TEXT,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (academic_day_id, period_code)
);

-- ─── 3. invitations ──────────────────────────────────────────────────────────
-- Tracks all staff and student invitations. Created by school admins.
-- API calls Supabase magic link on creation; status updated on acceptance.
-- metadata: year_group for students, subject/department for staff, etc.

CREATE TABLE IF NOT EXISTS invitations (
    id           UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    institute_id UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    email        TEXT NOT NULL,
    role         TEXT NOT NULL CHECK (role IN ('teacher','student','school_admin','department_head')),
    invited_by   UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    token        UUID NOT NULL DEFAULT uuid_generate_v4(),
    expires_at   TIMESTAMPTZ NOT NULL DEFAULT (now() + interval '7 days'),
    status       TEXT NOT NULL DEFAULT 'pending'
                     CHECK (status IN ('pending','accepted','expired','cancelled')),
    metadata     JSONB NOT NULL DEFAULT '{}',
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Only one pending invitation per (institute, email) at a time.
-- After acceptance/expiry/cancellation a new one may be issued.
CREATE UNIQUE INDEX IF NOT EXISTS idx_invitations_pending_unique
    ON invitations (institute_id, email)
    WHERE (status = 'pending');

-- ─── 4. taught_lessons ───────────────────────────────────────────────────────
-- One row per actual lesson occurrence, materialized from the teacher's
-- timetable slot template × matching academic_periods across the year.
-- School admin controls the frame (periods, rooms, substitutions).
-- Teachers control the content (lesson_plan, notes, tags, status).

CREATE TABLE IF NOT EXISTS taught_lessons (
    id                        UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    academic_period_id        UUID NOT NULL REFERENCES academic_periods(id) ON DELETE CASCADE,
    teacher_timetable_slot_id UUID REFERENCES teacher_timetable_slots(id) ON DELETE SET NULL,
    class_id                  UUID NOT NULL REFERENCES classes(id) ON DELETE CASCADE,
    teacher_id                UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    institute_id              UUID NOT NULL REFERENCES institutes(id) ON DELETE CASCADE,
    -- Denormalized for fast timeline queries (avoids 4-table joins)
    date                      DATE NOT NULL,
    period_code               TEXT NOT NULL,
    week_cycle                TEXT NOT NULL DEFAULT '',
    day_of_week               TEXT NOT NULL,
    -- Teacher-owned content
    lesson_plan               JSONB NOT NULL DEFAULT '{}',
    whiteboard_room_id        UUID REFERENCES whiteboard_rooms(id) ON DELETE SET NULL,
    status                    TEXT NOT NULL DEFAULT 'planned'
                                  CHECK (status IN ('planned','in_progress','completed','cancelled','substituted')),
    substitute_teacher_id     UUID REFERENCES profiles(id) ON DELETE SET NULL,
    notes                     TEXT,
    tags                      TEXT[] NOT NULL DEFAULT '{}',
    neo4j_node_id             TEXT,
    created_at                TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at                TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (academic_period_id, teacher_id)
);

-- ─── 5. Extend existing tables with notes + tags ──────────────────────────────
-- ADD COLUMN IF NOT EXISTS is idempotent — safe to re-run.

ALTER TABLE academic_terms           ADD COLUMN IF NOT EXISTS notes TEXT;
ALTER TABLE academic_terms           ADD COLUMN IF NOT EXISTS tags  TEXT[] NOT NULL DEFAULT '{}';

ALTER TABLE academic_weeks           ADD COLUMN IF NOT EXISTS notes TEXT;
ALTER TABLE academic_weeks           ADD COLUMN IF NOT EXISTS tags  TEXT[] NOT NULL DEFAULT '{}';

ALTER TABLE academic_days            ADD COLUMN IF NOT EXISTS notes TEXT;
ALTER TABLE academic_days            ADD COLUMN IF NOT EXISTS tags  TEXT[] NOT NULL DEFAULT '{}';

-- week_cycle on teacher_timetable_slots: '' = applies both weeks, 'A'/'B' = specific cycle.
ALTER TABLE teacher_timetable_slots  ADD COLUMN IF NOT EXISTS week_cycle TEXT NOT NULL DEFAULT '';

-- Drop old UNIQUE and replace with cycle-aware version.
-- The old constraint was (teacher_timetable_id, day_of_week, period_code).
-- PostgreSQL's generated name may differ/truncate across bootstrap history, so detect
-- the actual constraint by constrained column names instead of a stale hard-coded name.
DO $$
DECLARE
    old_constraint_name TEXT;
BEGIN
    SELECT con.conname INTO old_constraint_name
    FROM pg_constraint con
    JOIN pg_class rel ON rel.oid = con.conrelid
    JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
    WHERE nsp.nspname = 'public'
      AND rel.relname = 'teacher_timetable_slots'
      AND con.contype = 'u'
      AND (
          SELECT array_agg(att.attname::text ORDER BY ord.ordinality)
          FROM unnest(con.conkey) WITH ORDINALITY AS ord(attnum, ordinality)
          JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ord.attnum
      ) = ARRAY['teacher_timetable_id', 'day_of_week', 'period_code']::text[]
    LIMIT 1;

    IF old_constraint_name IS NOT NULL THEN
        EXECUTE format('ALTER TABLE public.teacher_timetable_slots DROP CONSTRAINT %I', old_constraint_name);
    END IF;
END $$;

DO $$ BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint
        WHERE conname = 'tts_unique_slot'
          AND conrelid = 'public.teacher_timetable_slots'::regclass
    ) THEN
        ALTER TABLE public.teacher_timetable_slots
            ADD CONSTRAINT tts_unique_slot UNIQUE (teacher_timetable_id, week_cycle, day_of_week, period_code);
    END IF;
END $$;

-- ─── 6. Indexes ───────────────────────────────────────────────────────────────

CREATE INDEX IF NOT EXISTS idx_term_breaks_tt        ON academic_term_breaks(school_timetable_id);
CREATE INDEX IF NOT EXISTS idx_term_breaks_inst      ON academic_term_breaks(institute_id);

CREATE INDEX IF NOT EXISTS idx_ap_day                ON academic_periods(academic_day_id);
CREATE INDEX IF NOT EXISTS idx_ap_inst               ON academic_periods(institute_id);
CREATE INDEX IF NOT EXISTS idx_ap_type               ON academic_periods(period_type);

CREATE INDEX IF NOT EXISTS idx_inv_inst              ON invitations(institute_id);
CREATE INDEX IF NOT EXISTS idx_inv_email             ON invitations(email);
CREATE INDEX IF NOT EXISTS idx_inv_token             ON invitations(token);
CREATE INDEX IF NOT EXISTS idx_inv_status            ON invitations(status);

CREATE INDEX IF NOT EXISTS idx_tl_period             ON taught_lessons(academic_period_id);
CREATE INDEX IF NOT EXISTS idx_tl_teacher            ON taught_lessons(teacher_id);
CREATE INDEX IF NOT EXISTS idx_tl_class              ON taught_lessons(class_id);
CREATE INDEX IF NOT EXISTS idx_tl_inst               ON taught_lessons(institute_id);
CREATE INDEX IF NOT EXISTS idx_tl_date               ON taught_lessons(date);
CREATE INDEX IF NOT EXISTS idx_tl_inst_date          ON taught_lessons(institute_id, date);

-- ─── 7. updated_at trigger ────────────────────────────────────────────────────

DO $$ DECLARE t TEXT; BEGIN
    FOREACH t IN ARRAY ARRAY['taught_lessons'] LOOP
        EXECUTE format(
            'DROP TRIGGER IF EXISTS trg_updated_at ON %I;
             CREATE TRIGGER trg_updated_at BEFORE UPDATE ON %I
             FOR EACH ROW EXECUTE FUNCTION set_updated_at();',
            t, t
        );
    END LOOP;
END $$;

-- ─── 8. Row Level Security ────────────────────────────────────────────────────

ALTER TABLE academic_term_breaks  ENABLE ROW LEVEL SECURITY;
ALTER TABLE academic_periods      ENABLE ROW LEVEL SECURITY;
ALTER TABLE invitations           ENABLE ROW LEVEL SECURITY;
ALTER TABLE taught_lessons        ENABLE ROW LEVEL SECURITY;

-- ── academic_term_breaks ──────────────────────────────────────────────────────
-- Any institute member can read; all writes via service_role (API).

CREATE POLICY "atb_inst_read" ON academic_term_breaks FOR SELECT
    USING (institute_id IN (
        SELECT institute_id FROM institute_memberships WHERE profile_id = auth.uid()
    ));
CREATE POLICY "atb_service" ON academic_term_breaks FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- ── academic_periods ──────────────────────────────────────────────────────────
-- Any institute member can read; all writes via service_role (API).

CREATE POLICY "ap_inst_read" ON academic_periods FOR SELECT
    USING (institute_id IN (
        SELECT institute_id FROM institute_memberships WHERE profile_id = auth.uid()
    ));
CREATE POLICY "ap_service" ON academic_periods FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- ── invitations ───────────────────────────────────────────────────────────────
-- School admins and the inviter can view their school's invitations.
-- All mutations via service_role (invitations created server-side only).

CREATE POLICY "inv_admin_read" ON invitations FOR SELECT
    USING (
        invited_by = auth.uid()
        OR institute_id IN (
            SELECT institute_id FROM institute_memberships
            WHERE profile_id = auth.uid()
              AND role IN ('school_admin', 'department_head')
        )
    );
CREATE POLICY "inv_service" ON invitations FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');

-- ── taught_lessons ────────────────────────────────────────────────────────────
-- Teachers read their own lessons; school admins read all in their school.
-- Teachers can UPDATE their own lesson content (plan, notes, tags, status).
-- Frame changes (room, substitute) and lesson creation: service_role only.

CREATE POLICY "tl_read" ON taught_lessons FOR SELECT
    USING (
        teacher_id = auth.uid()
        OR institute_id IN (
            SELECT institute_id FROM institute_memberships
            WHERE profile_id = auth.uid()
              AND role IN ('school_admin', 'department_head')
        )
    );
CREATE POLICY "tl_teacher_update" ON taught_lessons FOR UPDATE
    USING (teacher_id = auth.uid())
    WITH CHECK (teacher_id = auth.uid());
CREATE POLICY "tl_service" ON taught_lessons FOR ALL
    USING (auth.role() = 'service_role') WITH CHECK (auth.role() = 'service_role');
