-- Bumps the workspace deadline by the template's configured "activity_bump"
-- duration (default 1h). If the workspace bump will cross an autostart
-- threshold, then the bump is autostart + TTL. This is the deadline behavior if
-- the workspace was to autostart from a stopped state.
--
-- Max deadline is respected, and the deadline will never be bumped past it.
-- The deadline will never decrease.
-- name: ActivityBumpWorkspace :exec
WITH latest AS (
	SELECT
		workspace_builds.id::uuid AS build_id,
		workspace_builds.deadline::timestamp with time zone AS build_deadline,
		workspace_builds.max_deadline::timestamp with time zone AS build_max_deadline,
		workspace_builds.transition AS build_transition,
		provisioner_jobs.completed_at::timestamp with time zone AS job_completed_at,
		templates.activity_bump AS activity_bump,
		(
			CASE
				-- If the extension would push us over the next_autostart
				-- interval, then extend the deadline by the full TTL (NOT
				-- activity bump) from the autostart time. This will essentially
				-- be as if the workspace auto started at the given time and the
				-- original TTL was applied.
				--
				-- Sadly we can't define 'activity_bump_interval' above since
				-- it won't be available for this CASE statement, so we have to
				-- copy the cast twice.
				WHEN NOW() + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval > @next_autostart :: timestamptz
				    -- If the autostart is behind now(), then the
					-- autostart schedule is either the 0 time and not provided,
					-- or it was the autostart in the past, which is no longer
					-- relevant. If autostart is > 0 and in the past, then
					-- that is a mistake by the caller.
					AND @next_autostart > NOW()
					THEN
					-- Extend to the autostart, then add the activity bump
					((@next_autostart :: timestamptz) - NOW()) + CASE
						WHEN templates.allow_user_autostop
					    	THEN (workspaces.ttl / 1000 / 1000 / 1000 || ' seconds')::interval
							ELSE (templates.default_ttl / 1000 / 1000 / 1000 || ' seconds')::interval
					END

				-- Default to the activity bump duration.
				ELSE
					(templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval
			END
		) AS ttl_interval
	FROM workspace_builds
	JOIN provisioner_jobs
		ON provisioner_jobs.id = workspace_builds.job_id
	JOIN workspaces
		ON workspaces.id = workspace_builds.workspace_id
	JOIN templates
		ON templates.id = workspaces.template_id
	WHERE
		workspace_builds.workspace_id = @workspace_id::uuid
		-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
		-- are managed by the reconciliation loop and not subject to activity bumping
	  	AND workspaces.owner_id != 'c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID
	ORDER BY workspace_builds.build_number DESC
	LIMIT 1
)
UPDATE
	workspace_builds wb
SET
	updated_at = NOW(),
	deadline = CASE
		WHEN l.build_max_deadline = '0001-01-01 00:00:00+00'
		-- Never reduce the deadline from activity.
		THEN GREATEST(wb.deadline, NOW() + l.ttl_interval)
		ELSE LEAST(GREATEST(wb.deadline, NOW() + l.ttl_interval), l.build_max_deadline)
	END
FROM latest l
WHERE wb.id = l.build_id
AND l.job_completed_at IS NOT NULL
-- We only bump if the template has an activity bump duration set.
AND l.activity_bump > 0
AND l.build_transition = 'start'
-- We only bump if the raw interval is positive and non-zero.
AND l.ttl_interval > '0 seconds'::interval
-- We only bump if workspace shutdown is manual.
AND l.build_deadline != '0001-01-01 00:00:00+00'
-- We only bump when 5% of the deadline has elapsed.
AND l.build_deadline - (l.ttl_interval * 0.95) < NOW()
;
