# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""
Backend infrastructure for provisioning cloud workers.

See docs/reference/devel-blueprints/dynamic-compute.rst
"""

from celery import shared_task

from debusine.db.models import Worker


def terminate_idle(minutes: int = 10) -> bool:
    """
    Find and terminate idle workers.

    :returns: true if idle workers were found
    """
    # List cloud workers that have been idle for more than 10 minutes
    idle_workers = Worker.objects.with_idle_time().filter(
        idle_time__gt=minutes * 60,  # type: ignore[misc]
        worker_pool__isnull=False,
    )
    has_idle = False
    for worker in idle_workers:
        assert worker.worker_pool is not None
        has_idle = True
        worker.worker_pool.terminate_worker(worker)
    return has_idle


def create_new_workers() -> None:
    """Provision new workers if needed to handle pending load."""
    # TODO: process the backlog of pending work requests to provision new
    # workers if needed
    ...


# mypy complains that celery.shared_task is untyped, which is true, but we
# can't fix that here.
@shared_task()  # type: ignore[misc]
def provision() -> None:
    """Provision or terminate workers from pools to handle pending tasks."""
    # TODO: this is a minimum viable implementation: #326 will be the
    #       opportunity to for better cloud compute provisioning design

    if terminate_idle():
        # If workers have been idle for more than 10 minutes, we can assume
        # that we do not have a significant backlog of pending work requests.
        #
        # This may not be true: we may have a pool of idle arm64 workers and a
        # backlog of amd64 tasks. If that is the case, we will catch up with
        # the backlog in the next provision run.
        return

    create_new_workers()
