-- =====================================================
-- Audit Logging for Courses, Events, Bookings
--
-- Full change history for compliance: who changed what
-- field, old value -> new value, when. Mirrors the
-- member_audit_log pattern from 20260416000007.
-- =====================================================

-- -------------------------------------------------------
-- A) Add created_by / updated_by to main tables
-- -------------------------------------------------------

ALTER TABLE public.courses
  ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  ADD COLUMN IF NOT EXISTS updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL;

ALTER TABLE public.events
  ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  ADD COLUMN IF NOT EXISTS updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL;

ALTER TABLE public.bookings
  ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  ADD COLUMN IF NOT EXISTS updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL;

-- -------------------------------------------------------
-- B) Audit log tables
-- -------------------------------------------------------

-- B.1 Course audit log
CREATE TABLE IF NOT EXISTS public.course_audit_log (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  course_id uuid NOT NULL REFERENCES public.courses(id) ON DELETE CASCADE,
  account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
  user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  action text NOT NULL CHECK (action IN (
    'created', 'updated', 'status_changed', 'cancelled',
    'participant_enrolled', 'participant_cancelled',
    'participant_waitlisted', 'participant_promoted',
    'session_created', 'session_cancelled',
    'attendance_marked', 'instructor_changed', 'location_changed'
  )),
  changes jsonb NOT NULL DEFAULT '{}',
  metadata jsonb NOT NULL DEFAULT '{}',
  created_at timestamptz NOT NULL DEFAULT now()
);

COMMENT ON TABLE public.course_audit_log IS
  'Immutable audit trail for all course lifecycle events';

CREATE INDEX IF NOT EXISTS ix_course_audit_course
  ON public.course_audit_log(course_id, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_course_audit_account
  ON public.course_audit_log(account_id, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_course_audit_action
  ON public.course_audit_log(account_id, action);

ALTER TABLE public.course_audit_log ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.course_audit_log FROM authenticated, service_role;
GRANT SELECT ON public.course_audit_log TO authenticated;
GRANT INSERT, SELECT ON public.course_audit_log TO service_role;

CREATE POLICY course_audit_log_select
  ON public.course_audit_log FOR SELECT TO authenticated
  USING (public.has_role_on_account(account_id));

-- B.2 Event audit log
CREATE TABLE IF NOT EXISTS public.event_audit_log (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  event_id uuid NOT NULL REFERENCES public.events(id) ON DELETE CASCADE,
  account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
  user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  action text NOT NULL CHECK (action IN (
    'created', 'updated', 'status_changed', 'cancelled',
    'registration_confirmed', 'registration_waitlisted',
    'registration_cancelled', 'registration_promoted'
  )),
  changes jsonb NOT NULL DEFAULT '{}',
  metadata jsonb NOT NULL DEFAULT '{}',
  created_at timestamptz NOT NULL DEFAULT now()
);

COMMENT ON TABLE public.event_audit_log IS
  'Immutable audit trail for all event lifecycle events';

CREATE INDEX IF NOT EXISTS ix_event_audit_event
  ON public.event_audit_log(event_id, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_event_audit_account
  ON public.event_audit_log(account_id, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_event_audit_action
  ON public.event_audit_log(account_id, action);

ALTER TABLE public.event_audit_log ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.event_audit_log FROM authenticated, service_role;
GRANT SELECT ON public.event_audit_log TO authenticated;
GRANT INSERT, SELECT ON public.event_audit_log TO service_role;

CREATE POLICY event_audit_log_select
  ON public.event_audit_log FOR SELECT TO authenticated
  USING (public.has_role_on_account(account_id));

-- B.3 Booking audit log
CREATE TABLE IF NOT EXISTS public.booking_audit_log (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  booking_id uuid NOT NULL REFERENCES public.bookings(id) ON DELETE CASCADE,
  account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
  user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  action text NOT NULL CHECK (action IN (
    'created', 'updated', 'status_changed',
    'checked_in', 'checked_out', 'cancelled',
    'no_show', 'price_changed'
  )),
  changes jsonb NOT NULL DEFAULT '{}',
  metadata jsonb NOT NULL DEFAULT '{}',
  created_at timestamptz NOT NULL DEFAULT now()
);

COMMENT ON TABLE public.booking_audit_log IS
  'Immutable audit trail for all booking lifecycle events';

CREATE INDEX IF NOT EXISTS ix_booking_audit_booking
  ON public.booking_audit_log(booking_id, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_booking_audit_account
  ON public.booking_audit_log(account_id, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_booking_audit_action
  ON public.booking_audit_log(account_id, action);

ALTER TABLE public.booking_audit_log ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.booking_audit_log FROM authenticated, service_role;
GRANT SELECT ON public.booking_audit_log TO authenticated;
GRANT INSERT, SELECT ON public.booking_audit_log TO service_role;

CREATE POLICY booking_audit_log_select
  ON public.booking_audit_log FOR SELECT TO authenticated
  USING (public.has_role_on_account(account_id));

-- -------------------------------------------------------
-- C) Auto-audit triggers for UPDATE
-- -------------------------------------------------------

-- C.1 Courses UPDATE trigger
CREATE OR REPLACE FUNCTION public.trg_course_audit_on_update()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
  v_changes jsonb := '{}'::jsonb;
  v_action text := 'updated';
  v_user_id uuid;
BEGIN
  -- Build changes diff (field by field)
  IF OLD.name IS DISTINCT FROM NEW.name THEN
    v_changes := v_changes || jsonb_build_object('name', jsonb_build_object('old', OLD.name, 'new', NEW.name));
  END IF;
  IF OLD.description IS DISTINCT FROM NEW.description THEN
    v_changes := v_changes || jsonb_build_object('description', jsonb_build_object('old', OLD.description, 'new', NEW.description));
  END IF;
  IF OLD.course_number IS DISTINCT FROM NEW.course_number THEN
    v_changes := v_changes || jsonb_build_object('course_number', jsonb_build_object('old', OLD.course_number, 'new', NEW.course_number));
  END IF;
  IF OLD.category_id IS DISTINCT FROM NEW.category_id THEN
    v_changes := v_changes || jsonb_build_object('category_id', jsonb_build_object('old', OLD.category_id, 'new', NEW.category_id));
  END IF;
  IF OLD.instructor_id IS DISTINCT FROM NEW.instructor_id THEN
    v_changes := v_changes || jsonb_build_object('instructor_id', jsonb_build_object('old', OLD.instructor_id, 'new', NEW.instructor_id));
  END IF;
  IF OLD.location_id IS DISTINCT FROM NEW.location_id THEN
    v_changes := v_changes || jsonb_build_object('location_id', jsonb_build_object('old', OLD.location_id, 'new', NEW.location_id));
  END IF;
  IF OLD.start_date IS DISTINCT FROM NEW.start_date THEN
    v_changes := v_changes || jsonb_build_object('start_date', jsonb_build_object('old', OLD.start_date, 'new', NEW.start_date));
  END IF;
  IF OLD.end_date IS DISTINCT FROM NEW.end_date THEN
    v_changes := v_changes || jsonb_build_object('end_date', jsonb_build_object('old', OLD.end_date, 'new', NEW.end_date));
  END IF;
  IF OLD.fee IS DISTINCT FROM NEW.fee THEN
    v_changes := v_changes || jsonb_build_object('fee', jsonb_build_object('old', OLD.fee, 'new', NEW.fee));
  END IF;
  IF OLD.reduced_fee IS DISTINCT FROM NEW.reduced_fee THEN
    v_changes := v_changes || jsonb_build_object('reduced_fee', jsonb_build_object('old', OLD.reduced_fee, 'new', NEW.reduced_fee));
  END IF;
  IF OLD.capacity IS DISTINCT FROM NEW.capacity THEN
    v_changes := v_changes || jsonb_build_object('capacity', jsonb_build_object('old', OLD.capacity, 'new', NEW.capacity));
  END IF;
  IF OLD.min_participants IS DISTINCT FROM NEW.min_participants THEN
    v_changes := v_changes || jsonb_build_object('min_participants', jsonb_build_object('old', OLD.min_participants, 'new', NEW.min_participants));
  END IF;
  IF OLD.status IS DISTINCT FROM NEW.status THEN
    v_changes := v_changes || jsonb_build_object('status', jsonb_build_object('old', OLD.status, 'new', NEW.status));
  END IF;
  IF OLD.registration_deadline IS DISTINCT FROM NEW.registration_deadline THEN
    v_changes := v_changes || jsonb_build_object('registration_deadline', jsonb_build_object('old', OLD.registration_deadline, 'new', NEW.registration_deadline));
  END IF;
  IF OLD.notes IS DISTINCT FROM NEW.notes THEN
    v_changes := v_changes || jsonb_build_object('notes', jsonb_build_object('old', OLD.notes, 'new', NEW.notes));
  END IF;

  -- Skip if nothing actually changed
  IF v_changes = '{}'::jsonb THEN
    RETURN NULL;
  END IF;

  -- Classify the action
  IF OLD.status IS DISTINCT FROM NEW.status THEN
    v_action := 'status_changed';
  END IF;

  v_user_id := COALESCE(
    NULLIF(current_setting('app.current_user_id', true), '')::uuid,
    auth.uid()
  );

  INSERT INTO public.course_audit_log (course_id, account_id, user_id, action, changes)
  VALUES (NEW.id, NEW.account_id, v_user_id, v_action, v_changes);

  RETURN NULL;
END;
$$;

CREATE OR REPLACE TRIGGER trg_courses_audit_on_update
  AFTER UPDATE ON public.courses
  FOR EACH ROW
  EXECUTE FUNCTION public.trg_course_audit_on_update();

-- C.2 Events UPDATE trigger
CREATE OR REPLACE FUNCTION public.trg_event_audit_on_update()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
  v_changes jsonb := '{}'::jsonb;
  v_action text := 'updated';
  v_user_id uuid;
BEGIN
  -- Build changes diff (field by field)
  IF OLD.name IS DISTINCT FROM NEW.name THEN
    v_changes := v_changes || jsonb_build_object('name', jsonb_build_object('old', OLD.name, 'new', NEW.name));
  END IF;
  IF OLD.description IS DISTINCT FROM NEW.description THEN
    v_changes := v_changes || jsonb_build_object('description', jsonb_build_object('old', OLD.description, 'new', NEW.description));
  END IF;
  IF OLD.event_date IS DISTINCT FROM NEW.event_date THEN
    v_changes := v_changes || jsonb_build_object('event_date', jsonb_build_object('old', OLD.event_date, 'new', NEW.event_date));
  END IF;
  IF OLD.event_time IS DISTINCT FROM NEW.event_time THEN
    v_changes := v_changes || jsonb_build_object('event_time', jsonb_build_object('old', OLD.event_time, 'new', NEW.event_time));
  END IF;
  IF OLD.end_date IS DISTINCT FROM NEW.end_date THEN
    v_changes := v_changes || jsonb_build_object('end_date', jsonb_build_object('old', OLD.end_date, 'new', NEW.end_date));
  END IF;
  IF OLD.location IS DISTINCT FROM NEW.location THEN
    v_changes := v_changes || jsonb_build_object('location', jsonb_build_object('old', OLD.location, 'new', NEW.location));
  END IF;
  IF OLD.capacity IS DISTINCT FROM NEW.capacity THEN
    v_changes := v_changes || jsonb_build_object('capacity', jsonb_build_object('old', OLD.capacity, 'new', NEW.capacity));
  END IF;
  IF OLD.min_age IS DISTINCT FROM NEW.min_age THEN
    v_changes := v_changes || jsonb_build_object('min_age', jsonb_build_object('old', OLD.min_age, 'new', NEW.min_age));
  END IF;
  IF OLD.max_age IS DISTINCT FROM NEW.max_age THEN
    v_changes := v_changes || jsonb_build_object('max_age', jsonb_build_object('old', OLD.max_age, 'new', NEW.max_age));
  END IF;
  IF OLD.fee IS DISTINCT FROM NEW.fee THEN
    v_changes := v_changes || jsonb_build_object('fee', jsonb_build_object('old', OLD.fee, 'new', NEW.fee));
  END IF;
  IF OLD.status IS DISTINCT FROM NEW.status THEN
    v_changes := v_changes || jsonb_build_object('status', jsonb_build_object('old', OLD.status, 'new', NEW.status));
  END IF;
  IF OLD.registration_deadline IS DISTINCT FROM NEW.registration_deadline THEN
    v_changes := v_changes || jsonb_build_object('registration_deadline', jsonb_build_object('old', OLD.registration_deadline, 'new', NEW.registration_deadline));
  END IF;
  IF OLD.contact_name IS DISTINCT FROM NEW.contact_name THEN
    v_changes := v_changes || jsonb_build_object('contact_name', jsonb_build_object('old', OLD.contact_name, 'new', NEW.contact_name));
  END IF;
  IF OLD.contact_email IS DISTINCT FROM NEW.contact_email THEN
    v_changes := v_changes || jsonb_build_object('contact_email', jsonb_build_object('old', OLD.contact_email, 'new', NEW.contact_email));
  END IF;
  IF OLD.contact_phone IS DISTINCT FROM NEW.contact_phone THEN
    v_changes := v_changes || jsonb_build_object('contact_phone', jsonb_build_object('old', OLD.contact_phone, 'new', NEW.contact_phone));
  END IF;

  -- Skip if nothing actually changed
  IF v_changes = '{}'::jsonb THEN
    RETURN NULL;
  END IF;

  -- Classify the action
  IF OLD.status IS DISTINCT FROM NEW.status THEN
    v_action := 'status_changed';
  END IF;

  v_user_id := COALESCE(
    NULLIF(current_setting('app.current_user_id', true), '')::uuid,
    auth.uid()
  );

  INSERT INTO public.event_audit_log (event_id, account_id, user_id, action, changes)
  VALUES (NEW.id, NEW.account_id, v_user_id, v_action, v_changes);

  RETURN NULL;
END;
$$;

CREATE OR REPLACE TRIGGER trg_events_audit_on_update
  AFTER UPDATE ON public.events
  FOR EACH ROW
  EXECUTE FUNCTION public.trg_event_audit_on_update();

-- C.3 Bookings UPDATE trigger
CREATE OR REPLACE FUNCTION public.trg_booking_audit_on_update()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
  v_changes jsonb := '{}'::jsonb;
  v_action text := 'updated';
  v_user_id uuid;
BEGIN
  -- Build changes diff (field by field)
  IF OLD.room_id IS DISTINCT FROM NEW.room_id THEN
    v_changes := v_changes || jsonb_build_object('room_id', jsonb_build_object('old', OLD.room_id, 'new', NEW.room_id));
  END IF;
  IF OLD.guest_id IS DISTINCT FROM NEW.guest_id THEN
    v_changes := v_changes || jsonb_build_object('guest_id', jsonb_build_object('old', OLD.guest_id, 'new', NEW.guest_id));
  END IF;
  IF OLD.check_in IS DISTINCT FROM NEW.check_in THEN
    v_changes := v_changes || jsonb_build_object('check_in', jsonb_build_object('old', OLD.check_in, 'new', NEW.check_in));
  END IF;
  IF OLD.check_out IS DISTINCT FROM NEW.check_out THEN
    v_changes := v_changes || jsonb_build_object('check_out', jsonb_build_object('old', OLD.check_out, 'new', NEW.check_out));
  END IF;
  IF OLD.adults IS DISTINCT FROM NEW.adults THEN
    v_changes := v_changes || jsonb_build_object('adults', jsonb_build_object('old', OLD.adults, 'new', NEW.adults));
  END IF;
  IF OLD.children IS DISTINCT FROM NEW.children THEN
    v_changes := v_changes || jsonb_build_object('children', jsonb_build_object('old', OLD.children, 'new', NEW.children));
  END IF;
  IF OLD.status IS DISTINCT FROM NEW.status THEN
    v_changes := v_changes || jsonb_build_object('status', jsonb_build_object('old', OLD.status, 'new', NEW.status));
  END IF;
  IF OLD.total_price IS DISTINCT FROM NEW.total_price THEN
    v_changes := v_changes || jsonb_build_object('total_price', jsonb_build_object('old', OLD.total_price, 'new', NEW.total_price));
  END IF;
  IF OLD.notes IS DISTINCT FROM NEW.notes THEN
    v_changes := v_changes || jsonb_build_object('notes', jsonb_build_object('old', OLD.notes, 'new', NEW.notes));
  END IF;

  -- Skip if nothing actually changed
  IF v_changes = '{}'::jsonb THEN
    RETURN NULL;
  END IF;

  -- Classify the action
  IF OLD.status IS DISTINCT FROM NEW.status THEN
    v_action := 'status_changed';
  END IF;

  v_user_id := COALESCE(
    NULLIF(current_setting('app.current_user_id', true), '')::uuid,
    auth.uid()
  );

  INSERT INTO public.booking_audit_log (booking_id, account_id, user_id, action, changes)
  VALUES (NEW.id, NEW.account_id, v_user_id, v_action, v_changes);

  RETURN NULL;
END;
$$;

CREATE OR REPLACE TRIGGER trg_bookings_audit_on_update
  AFTER UPDATE ON public.bookings
  FOR EACH ROW
  EXECUTE FUNCTION public.trg_booking_audit_on_update();

-- -------------------------------------------------------
-- D) Auto-audit triggers for INSERT
-- -------------------------------------------------------

-- D.1 Courses INSERT trigger
CREATE OR REPLACE FUNCTION public.trg_course_audit_on_insert()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
  v_user_id uuid;
BEGIN
  v_user_id := COALESCE(
    NULLIF(current_setting('app.current_user_id', true), '')::uuid,
    NEW.created_by
  );

  INSERT INTO public.course_audit_log (course_id, account_id, user_id, action, metadata)
  VALUES (
    NEW.id, NEW.account_id, v_user_id, 'created',
    jsonb_build_object(
      'course_number', NEW.course_number,
      'name', NEW.name,
      'status', NEW.status,
      'fee', NEW.fee,
      'capacity', NEW.capacity,
      'start_date', NEW.start_date,
      'end_date', NEW.end_date
    )
  );

  RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER trg_courses_audit_on_insert
  AFTER INSERT ON public.courses
  FOR EACH ROW
  EXECUTE FUNCTION public.trg_course_audit_on_insert();

-- D.2 Events INSERT trigger
CREATE OR REPLACE FUNCTION public.trg_event_audit_on_insert()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
  v_user_id uuid;
BEGIN
  v_user_id := COALESCE(
    NULLIF(current_setting('app.current_user_id', true), '')::uuid,
    NEW.created_by
  );

  INSERT INTO public.event_audit_log (event_id, account_id, user_id, action, metadata)
  VALUES (
    NEW.id, NEW.account_id, v_user_id, 'created',
    jsonb_build_object(
      'name', NEW.name,
      'status', NEW.status,
      'event_date', NEW.event_date,
      'location', NEW.location,
      'capacity', NEW.capacity,
      'fee', NEW.fee
    )
  );

  RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER trg_events_audit_on_insert
  AFTER INSERT ON public.events
  FOR EACH ROW
  EXECUTE FUNCTION public.trg_event_audit_on_insert();

-- D.3 Bookings INSERT trigger
CREATE OR REPLACE FUNCTION public.trg_booking_audit_on_insert()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
  v_user_id uuid;
BEGIN
  v_user_id := COALESCE(
    NULLIF(current_setting('app.current_user_id', true), '')::uuid,
    NEW.created_by
  );

  INSERT INTO public.booking_audit_log (booking_id, account_id, user_id, action, metadata)
  VALUES (
    NEW.id, NEW.account_id, v_user_id, 'created',
    jsonb_build_object(
      'room_id', NEW.room_id,
      'guest_id', NEW.guest_id,
      'check_in', NEW.check_in,
      'check_out', NEW.check_out,
      'status', NEW.status,
      'total_price', NEW.total_price,
      'adults', NEW.adults,
      'children', NEW.children
    )
  );

  RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER trg_bookings_audit_on_insert
  AFTER INSERT ON public.bookings
  FOR EACH ROW
  EXECUTE FUNCTION public.trg_booking_audit_on_insert();
