-- Fix task status logic: pending provisioner job should give pending task status, not initializing.
-- A task is pending when the provisioner hasn't picked up the job yet.
-- A task is initializing when the provisioner is actively running the job.
DROP VIEW IF EXISTS tasks_with_status;

CREATE VIEW
	tasks_with_status
AS
	SELECT
		tasks.*,
		-- Combine component statuses with precedence: build -> agent -> app.
		CASE
			WHEN tasks.workspace_id IS NULL THEN 'pending'::task_status
			WHEN build_status.status != 'active' THEN build_status.status::task_status
			WHEN agent_status.status != 'active' THEN agent_status.status::task_status
			ELSE app_status.status::task_status
		END AS status,
		-- Attach debug information for troubleshooting status.
		jsonb_build_object(
			'build', jsonb_build_object(
				'transition', latest_build_raw.transition,
				'job_status', latest_build_raw.job_status,
				'computed', build_status.status
			),
			'agent', jsonb_build_object(
				'lifecycle_state', agent_raw.lifecycle_state,
				'computed', agent_status.status
			),
			'app', jsonb_build_object(
				'health', app_raw.health,
				'computed', app_status.status
			)
		) AS status_debug,
		task_app.*,
		agent_raw.lifecycle_state AS workspace_agent_lifecycle_state,
		app_raw.health AS workspace_app_health,
		task_owner.*
	FROM
		tasks
	CROSS JOIN LATERAL (
		SELECT
			vu.username AS owner_username,
			vu.name AS owner_name,
			vu.avatar_url AS owner_avatar_url
		FROM
			visible_users vu
		WHERE
			vu.id = tasks.owner_id
	) task_owner
	LEFT JOIN LATERAL (
		SELECT
			task_app.workspace_build_number,
			task_app.workspace_agent_id,
			task_app.workspace_app_id
		FROM
			task_workspace_apps task_app
		WHERE
			task_id = tasks.id
		ORDER BY
			task_app.workspace_build_number DESC
		LIMIT 1
	) task_app ON TRUE

	-- Join the raw data for computing task status.
	LEFT JOIN LATERAL (
		SELECT
			workspace_build.transition,
			provisioner_job.job_status,
			workspace_build.job_id
		FROM
			workspace_builds workspace_build
		JOIN
			provisioner_jobs provisioner_job
			ON provisioner_job.id = workspace_build.job_id
		WHERE
			workspace_build.workspace_id = tasks.workspace_id
			AND workspace_build.build_number = task_app.workspace_build_number
	) latest_build_raw ON TRUE
	LEFT JOIN LATERAL (
		SELECT
			workspace_agent.lifecycle_state
		FROM
			workspace_agents workspace_agent
		WHERE
			workspace_agent.id = task_app.workspace_agent_id
	) agent_raw ON TRUE
	LEFT JOIN LATERAL (
		SELECT
			workspace_app.health
		FROM
			workspace_apps workspace_app
		WHERE
			workspace_app.id = task_app.workspace_app_id
	) app_raw ON TRUE

	-- Compute the status for each component.
	CROSS JOIN LATERAL (
		SELECT
			CASE
				WHEN latest_build_raw.job_status IS NULL THEN 'pending'::task_status
				WHEN latest_build_raw.job_status IN ('failed', 'canceling', 'canceled') THEN 'error'::task_status
				WHEN
					latest_build_raw.transition IN ('stop', 'delete')
					AND latest_build_raw.job_status = 'succeeded' THEN 'paused'::task_status
				-- Job is pending (not picked up by provisioner yet).
				WHEN
					latest_build_raw.transition = 'start'
					AND latest_build_raw.job_status = 'pending' THEN 'pending'::task_status
				-- Job is running or done, defer to agent/app status.
				WHEN
					latest_build_raw.transition = 'start'
					AND latest_build_raw.job_status IN ('running', 'succeeded') THEN 'active'::task_status
				ELSE 'unknown'::task_status
			END AS status
	) build_status
	CROSS JOIN LATERAL (
		SELECT
			CASE
				-- No agent or connecting.
				WHEN
					agent_raw.lifecycle_state IS NULL
					OR agent_raw.lifecycle_state IN ('created', 'starting') THEN 'initializing'::task_status
				-- Agent is running, defer to app status.
				-- NOTE(mafredri): The start_error/start_timeout states means connected, but some startup script failed.
				-- This may or may not affect the task status but this has to be caught by app health check.
				WHEN agent_raw.lifecycle_state IN ('ready', 'start_timeout', 'start_error') THEN 'active'::task_status
				-- If the agent is shutting down or turned off, this is an unknown state because we would expect a stop
				-- build to be running.
				-- This is essentially equal to: `IN ('shutting_down', 'shutdown_timeout', 'shutdown_error', 'off')`,
				-- but we cannot use them because the values were added in a migration.
				WHEN agent_raw.lifecycle_state NOT IN ('created', 'starting', 'ready', 'start_timeout', 'start_error') THEN 'unknown'::task_status
				ELSE 'unknown'::task_status
			END AS status
	) agent_status
	CROSS JOIN LATERAL (
		SELECT
			CASE
				WHEN app_raw.health = 'initializing' THEN 'initializing'::task_status
				WHEN app_raw.health = 'unhealthy' THEN 'error'::task_status
				WHEN app_raw.health IN ('healthy', 'disabled') THEN 'active'::task_status
				ELSE 'unknown'::task_status
			END AS status
	) app_status
	WHERE
		tasks.deleted_at IS NULL;
