-- User management foundation (U1 of #376):
--
--   - users.display_name: optional human-readable name distinct from username.
--   - user_invites: admin-generated tokens; user redeems during signup.
--   - registration_settings (singleton): controls whether registration is
--     'invite_only' (default after first admin) or 'open' (admin can
--     re-enable). The handler in U1-T2 also has a special-case for the
--     "no users yet" state where it ignores invites and lets the first
--     register as admin.
--   - audit_log: append-only record of admin-driven user mutations.
--     No retention policy in v1.

ALTER TABLE users ADD COLUMN IF NOT EXISTS display_name text;

CREATE TABLE user_invites (
    token         text PRIMARY KEY,
    invited_by    uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    note          text,
    created_at    timestamptz NOT NULL DEFAULT now(),
    expires_at    timestamptz NOT NULL,
    redeemed_at   timestamptz,
    redeemed_by   uuid REFERENCES users(id) ON DELETE SET NULL
);
CREATE INDEX user_invites_active_idx
    ON user_invites (expires_at)
    WHERE redeemed_at IS NULL;

CREATE TABLE registration_settings (
    id   boolean PRIMARY KEY DEFAULT true,
    mode text NOT NULL DEFAULT 'invite_only',
    CONSTRAINT registration_settings_singleton CHECK (id = true),
    CONSTRAINT registration_settings_mode_check
        CHECK (mode IN ('invite_only', 'open'))
);
INSERT INTO registration_settings (id) VALUES (true)
    ON CONFLICT (id) DO NOTHING;

CREATE TABLE audit_log (
    id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    actor_id    uuid REFERENCES users(id) ON DELETE SET NULL,
    target_id   uuid REFERENCES users(id) ON DELETE SET NULL,
    action      text NOT NULL,
    metadata    jsonb,
    created_at  timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX audit_log_created_at_idx ON audit_log (created_at DESC);
CREATE INDEX audit_log_actor_id_idx   ON audit_log (actor_id);
CREATE INDEX audit_log_target_id_idx  ON audit_log (target_id);
