-- Audit log table
CREATE TABLE app.audit_log (
  audit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  table_name TEXT NOT NULL,
  record_id TEXT NOT NULL,
  action TEXT NOT NULL,        -- 'INSERT', 'UPDATE', 'DELETE'
  user_id UUID,                -- from session variable, nullable for system actions
  changed_fields JSONB,        -- UPDATE: {field: {old, new}}; INSERT: full row; DELETE: full row
  occurred_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_audit_log_table_record ON app.audit_log(table_name, record_id);
CREATE INDEX idx_audit_log_user ON app.audit_log(user_id);
CREATE INDEX idx_audit_log_occurred ON app.audit_log(occurred_at);

-- Generic trigger function
CREATE OR REPLACE FUNCTION app.audit_trigger_func() RETURNS TRIGGER AS $$
DECLARE
  _user_id UUID;
  _record_id TEXT;
  _changed JSONB;
  _old JSONB;
  _new JSONB;
  _key TEXT;
BEGIN
  -- Get current user from session variable (set per-request by app)
  BEGIN
    _user_id := current_setting('app.current_user_id', true)::UUID;
  EXCEPTION WHEN OTHERS THEN
    _user_id := NULL;
  END;

  IF TG_OP = 'DELETE' THEN
    _record_id := (row_to_json(OLD) ->> TG_ARGV[0])::TEXT;
    INSERT INTO app.audit_log (table_name, record_id, action, user_id, changed_fields)
    VALUES (TG_TABLE_NAME, _record_id, 'DELETE', _user_id, row_to_json(OLD)::JSONB);
    RETURN OLD;

  ELSIF TG_OP = 'INSERT' THEN
    _record_id := (row_to_json(NEW) ->> TG_ARGV[0])::TEXT;
    INSERT INTO app.audit_log (table_name, record_id, action, user_id, changed_fields)
    VALUES (TG_TABLE_NAME, _record_id, 'INSERT', _user_id, row_to_json(NEW)::JSONB);
    RETURN NEW;

  ELSIF TG_OP = 'UPDATE' THEN
    _record_id := (row_to_json(NEW) ->> TG_ARGV[0])::TEXT;
    _old := row_to_json(OLD)::JSONB;
    _new := row_to_json(NEW)::JSONB;
    _changed := '{}'::JSONB;

    FOR _key IN SELECT jsonb_object_keys(_new)
    LOOP
      IF _key IN ('updated_at', 'created_at') THEN CONTINUE; END IF;
      IF (_old ->> _key) IS DISTINCT FROM (_new ->> _key) THEN
        _changed := _changed || jsonb_build_object(
          _key, jsonb_build_object('old', _old -> _key, 'new', _new -> _key)
        );
      END IF;
    END LOOP;

    -- Skip if nothing actually changed
    IF _changed = '{}'::JSONB THEN RETURN NEW; END IF;

    INSERT INTO app.audit_log (table_name, record_id, action, user_id, changed_fields)
    VALUES (TG_TABLE_NAME, _record_id, 'UPDATE', _user_id, _changed);
    RETURN NEW;
  END IF;

  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- Helper to attach audit trigger to a table
CREATE OR REPLACE FUNCTION app.enable_audit(target_table TEXT, pk_column TEXT DEFAULT 'id') RETURNS VOID AS $$
DECLARE
  trigger_name TEXT;
BEGIN
  trigger_name := 'audit_' || replace(target_table, '"', '');
  EXECUTE format(
    'CREATE TRIGGER %I AFTER INSERT OR UPDATE OR DELETE ON app.%s FOR EACH ROW EXECUTE FUNCTION app.audit_trigger_func(%L)',
    trigger_name, target_table, pk_column
  );
END;
$$ LANGUAGE plpgsql;

-- Enable audit on existing tables
SELECT app.enable_audit('"user"', 'user_id');
SELECT app.enable_audit('user_profile', 'profile_id');
