-- Deletion paths.
--
-- Discord's developer policy requires deleting user data on request, and API
-- registrants are an international user base whose data protection expectations
-- apply regardless. Build this before you need it: retrofitting a deletion path
-- across three stores is miserable, and being asked for one you do not have is
-- worse.
--
-- These functions cover Postgres. The caller must ALSO clear Redis - cached
-- callsign lookups, rate-limit counters keyed by hashed Discord ID, and any
-- verification challenge in flight. Postgres is not the whole story.

-- ---------------------------------------------------------------------------

-- Removes a Discord user and everything referencing them. The ON DELETE CASCADE
-- chain reaches verifications, subscriptions, imports and qsos, so this is one
-- statement rather than a list that can fall out of date as tables are added.
--
-- Returns the number of QSOs removed, purely so the caller can tell the user
-- what was deleted.
CREATE FUNCTION core.forget_user(p_discord_id bigint)
RETURNS TABLE (deleted_user boolean, deleted_qsos bigint)
LANGUAGE plpgsql
-- Pinned: the body qualifies its tables, but now(), count() and friends resolve
-- through search_path. Matters more if this ever becomes SECURITY DEFINER.
SET search_path = pg_catalog, core, logbook
AS $$
DECLARE
    v_user_id bigint;
    v_qsos    bigint := 0;
BEGIN
    SELECT id INTO v_user_id FROM core.users WHERE discord_id = p_discord_id;

    IF v_user_id IS NULL THEN
        RETURN QUERY SELECT false, 0::bigint;
        RETURN;
    END IF;

    SELECT count(*) INTO v_qsos FROM logbook.qsos WHERE user_id = v_user_id;

    -- Cascades do the rest.
    DELETE FROM core.users WHERE id = v_user_id;

    RETURN QUERY SELECT true, v_qsos;
END;
$$;

COMMENT ON FUNCTION core.forget_user(bigint) IS 'Hard-deletes a Discord user and all dependent rows. Caller must also purge Redis keys for this user.';

-- ---------------------------------------------------------------------------

-- The API-side equivalent. Keys and usage rows cascade from the owner.
CREATE FUNCTION api.forget_owner(p_email core.email)
RETURNS TABLE (deleted_owner boolean, deleted_keys bigint)
LANGUAGE plpgsql
SET search_path = pg_catalog, core, api
AS $$
DECLARE
    v_owner_id bigint;
    v_keys     bigint := 0;
BEGIN
    SELECT id INTO v_owner_id FROM api.owners WHERE email = p_email;

    IF v_owner_id IS NULL THEN
        RETURN QUERY SELECT false, 0::bigint;
        RETURN;
    END IF;

    SELECT count(*) INTO v_keys FROM api.keys WHERE owner_id = v_owner_id;

    DELETE FROM api.owners WHERE id = v_owner_id;

    RETURN QUERY SELECT true, v_keys;
END;
$$;

-- ---------------------------------------------------------------------------

-- Retention. Verification records carry a hashed challenge and a timestamp, and
-- are useful for a short while when investigating a failed verification. They
-- are not useful a year later. Run this from cron.
CREATE FUNCTION core.prune_verifications(p_keep_days integer DEFAULT 90)
RETURNS bigint
LANGUAGE plpgsql
SET search_path = pg_catalog, core
AS $$
DECLARE
    v_removed bigint;
BEGIN
    DELETE FROM core.verifications
     WHERE created_at < now() - make_interval(days => p_keep_days)
       AND status <> 'succeeded';

    GET DIAGNOSTICS v_removed = ROW_COUNT;

    RETURN v_removed;
END;
$$;

GRANT EXECUTE ON FUNCTION core.forget_user(bigint) TO hammy_app;
GRANT EXECUTE ON FUNCTION api.forget_owner(core.email) TO hammy_app;
GRANT EXECUTE ON FUNCTION core.prune_verifications(integer) TO hammy_app;
