ALTER TABLE tasks
  ADD COLUMN deletion_started_at TIMESTAMPTZ,
  ADD COLUMN deletion_reason VARCHAR(32);

CREATE INDEX tasks_deletion_pending
  ON tasks(deletion_started_at)
  WHERE deletion_started_at IS NOT NULL;

CREATE TABLE storage_objects (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  task_id UUID NOT NULL,
  task_file_id UUID,
  object_kind VARCHAR(32) NOT NULL,
  state VARCHAR(20) NOT NULL DEFAULT 'staging',
  backend VARCHAR(16) NOT NULL,
  storage_endpoint_id UUID REFERENCES storage_endpoints(id) ON DELETE RESTRICT,
  object_key TEXT NOT NULL,
  storage_etag TEXT,
  size_bytes BIGINT,
  lease_owner UUID,
  lease_until TIMESTAMPTZ,
  delete_attempts INTEGER NOT NULL DEFAULT 0,
  next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  last_error TEXT,
  published_at TIMESTAMPTZ,
  deleted_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  CONSTRAINT storage_objects_kind_check
    CHECK (object_kind IN ('result', 'zip_attempt', 'input', 'input_dir', 'legacy_zip')),
  CONSTRAINT storage_objects_state_check
    CHECK (state IN ('staging', 'published', 'delete_pending', 'deleted')),
  CONSTRAINT storage_objects_backend_check
    CHECK (backend IN ('s3', 'local', 'local_dir')),
  CONSTRAINT storage_objects_attempts_check
    CHECK (delete_attempts >= 0),
  CONSTRAINT storage_objects_size_check
    CHECK (size_bytes IS NULL OR size_bytes >= 0),
  CONSTRAINT storage_objects_endpoint_check
    CHECK (
      (backend = 's3' AND storage_endpoint_id IS NOT NULL)
      OR (backend IN ('local', 'local_dir') AND storage_endpoint_id IS NULL)
    ),
  CONSTRAINT storage_objects_lease_pair_check
    CHECK ((lease_owner IS NULL) = (lease_until IS NULL))
);

CREATE UNIQUE INDEX storage_objects_locator_unique
  ON storage_objects(
    backend,
    COALESCE(storage_endpoint_id, '00000000-0000-0000-0000-000000000000'::uuid),
    object_key
  );

CREATE INDEX storage_objects_cleanup_ready
  ON storage_objects(next_attempt_at, created_at)
  WHERE state IN ('staging', 'delete_pending');

CREATE INDEX storage_objects_task
  ON storage_objects(task_id, state);

INSERT INTO storage_objects (
  task_id, task_file_id, object_kind, state,
  backend, storage_endpoint_id, object_key, storage_etag,
  size_bytes, published_at
)
SELECT
  file.task_id,
  file.id,
  'result',
  'published',
  file.storage_backend,
  file.storage_endpoint_id,
  COALESCE(file.storage_key, file.storage_path),
  file.storage_etag,
  file.compressed_size,
  COALESCE(file.completed_at, file.created_at)
FROM task_files AS file
WHERE file.status = 'completed'
  AND COALESCE(file.storage_key, file.storage_path) IS NOT NULL
ON CONFLICT DO NOTHING;

INSERT INTO storage_objects (
  task_id, object_kind, state,
  backend, storage_endpoint_id, object_key, storage_etag,
  size_bytes, published_at
)
SELECT
  task.id,
  'zip_attempt',
  'published',
  task.zip_storage_backend,
  task.zip_storage_endpoint_id,
  task.zip_storage_key,
  task.zip_storage_etag,
  task.zip_size,
  COALESCE(task.completed_at, task.created_at)
FROM tasks AS task
WHERE task.zip_storage_backend IS NOT NULL
  AND task.zip_storage_key IS NOT NULL
ON CONFLICT DO NOTHING;
