from __future__ import annotations

from typing import TYPE_CHECKING, Any

from apify_client._docs import docs_group
from apify_client._models import (
    ActorStandby,
    Run,
    RunResponse,
    Task,
    TaskInput,
    TaskOptions,
    TaskPublicConfig,
    TaskResponse,
    UpdateTaskRequest,
)
from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync
from apify_client._utils.encoding import encode_webhooks_to_base64
from apify_client._utils.http import response_to_dict
from apify_client._utils.time import to_seconds

if TYPE_CHECKING:
    from datetime import timedelta

    from apify_client._literals import ActorJobStatus, RunOrigin
    from apify_client._resource_clients import (
        RunClient,
        RunClientAsync,
        RunCollectionClient,
        RunCollectionClientAsync,
        WebhookCollectionClient,
        WebhookCollectionClientAsync,
    )
    from apify_client._typeddicts import TaskInputDict
    from apify_client.types import Timeout, WebhooksList


@docs_group('Resource clients')
class TaskClient(ResourceClient):
    """Sub-client for managing a specific task.

    Provides methods to manage a specific task, e.g. update it, delete it, or start runs. Obtain an instance via an
    appropriate method on the `ApifyClient` class.
    """

    def __init__(
        self,
        *,
        resource_id: str,
        resource_path: str = 'actor-tasks',
        **kwargs: Any,
    ) -> None:
        super().__init__(resource_id=resource_id, resource_path=resource_path, **kwargs)

    def get(self, *, timeout: Timeout = 'short') -> Task | None:
        """Retrieve the task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/get-task

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            The retrieved task.
        """
        result = self._get(timeout=timeout)
        if result is None:
            return None
        return TaskResponse.model_validate(result).data

    def update(
        self,
        *,
        name: str | None = None,
        task_input: TaskInputDict | TaskInput | None = None,
        build: str | None = None,
        max_items: int | None = None,
        memory_mbytes: int | None = None,
        run_timeout: timedelta | None = None,
        restart_on_error: bool | None = None,
        title: str | None = None,
        actor_standby_desired_requests_per_actor_run: int | None = None,
        actor_standby_max_requests_per_actor_run: int | None = None,
        actor_standby_idle_timeout: timedelta | None = None,
        actor_standby_build: str | None = None,
        actor_standby_memory_mbytes: int | None = None,
        is_public: bool | None = None,
        public_config_seo_title: str | None = None,
        public_config_seo_description: str | None = None,
        public_config_input_schema_fields: list[str] | None = None,
        public_config_dataset_name: str | None = None,
        public_config_dataset_view: str | None = None,
        timeout: Timeout = 'short',
    ) -> Task:
        """Update the task with specified fields.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/update-task

        Args:
            name: Name of the task.
            build: Actor build to run. It can be either a build tag or build number. By default, the run uses
                the build specified in the task settings (typically latest).
            max_items: Maximum number of results that will be returned by this run. If the Actor is charged per result,
                you will not be charged for more results than the given limit.
            memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
                in the task settings.
            run_timeout: Optional timeout for the run. By default, the run uses timeout specified
                in the task settings.
            restart_on_error: If true, the Task run process will be restarted whenever it exits with
                a non-zero status code.
            task_input: Task input dictionary.
            title: A human-friendly equivalent of the name.
            actor_standby_desired_requests_per_actor_run: The desired number of concurrent HTTP requests for
                a single Actor Standby run.
            actor_standby_max_requests_per_actor_run: The maximum number of concurrent HTTP requests for
                a single Actor Standby run.
            actor_standby_idle_timeout: If the Actor run does not receive any requests for this time,
                it will be shut down.
            actor_standby_build: The build tag or number to run when the Actor is in Standby mode.
            actor_standby_memory_mbytes: The memory in megabytes to use when the Actor is in Standby mode.
            is_public: Set to `True` to publish the task on its public landing page, or `False` to unpublish it.
                Passing the value the task already has does nothing. Publishing requires the public display
                configuration to be filled in, and write access to the task's Actor.
            public_config_seo_title: SEO title of the public task page. Defaults to the task title when not set.
            public_config_seo_description: SEO description of the public task page. Defaults to the task description
                when not set.
            public_config_input_schema_fields: Names of the task input fields displayed on the public task page.
            public_config_dataset_name: Name of the dataset from the Actor's dataset schema whose results are
                displayed on the public task page.
            public_config_dataset_view: View key from the Actor's dataset schema shown on the public task page.
            timeout: Timeout for the API HTTP request.

        Returns:
            The updated task.
        """
        if task_input is not None and not isinstance(task_input, TaskInput):
            task_input = TaskInput.model_validate(task_input)

        task_fields = UpdateTaskRequest(
            name=name,
            title=title,
            input=task_input,
            is_public=is_public,
            public_config=TaskPublicConfig(
                seo_title=public_config_seo_title,
                seo_description=public_config_seo_description,
                input_schema_fields=public_config_input_schema_fields,
                dataset_name=public_config_dataset_name,
                dataset_view=public_config_dataset_view,
            ),
            options=TaskOptions(
                build=build,
                max_items=max_items,
                memory_mbytes=memory_mbytes,
                timeout_secs=to_seconds(run_timeout, as_int=True),
                restart_on_error=restart_on_error,
            ),
            actor_standby=ActorStandby(
                desired_requests_per_actor_run=actor_standby_desired_requests_per_actor_run,
                max_requests_per_actor_run=actor_standby_max_requests_per_actor_run,
                idle_timeout_secs=to_seconds(actor_standby_idle_timeout, as_int=True),
                build=actor_standby_build,
                memory_mbytes=actor_standby_memory_mbytes,
            ),
        )
        result = self._update(timeout=timeout, **task_fields.model_dump(by_alias=True, exclude_none=True))
        return TaskResponse.model_validate(result).data

    def publish(self, *, timeout: Timeout = 'short') -> Task:
        """Publish the task on its public landing page.

        Convenience wrapper over `update` with `is_public` set to `True`. The task's Actor must be public and
        the task must have its public display configuration set up. Requires write access to the task and to its
        Actor. Publishing an already published task does nothing.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/update-task

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            The published task.
        """
        return self.update(is_public=True, timeout=timeout)

    def unpublish(self, *, timeout: Timeout = 'short') -> Task:
        """Unpublish the task from its public landing page.

        Convenience wrapper over `update` with `is_public` set to `False`. The public display configuration is
        preserved, so the task can be published again without re-entering it. Requires write access to the task
        and to its Actor. Unpublishing a task that is not published does nothing.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/update-task

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            The unpublished task.
        """
        return self.update(is_public=False, timeout=timeout)

    def delete(self, *, timeout: Timeout = 'short') -> None:
        """Delete the task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/delete-task

        Args:
            timeout: Timeout for the API HTTP request.
        """
        self._delete(timeout=timeout)

    def start(
        self,
        *,
        task_input: TaskInputDict | TaskInput | None = None,
        build: str | None = None,
        max_items: int | None = None,
        memory_mbytes: int | None = None,
        run_timeout: timedelta | None = None,
        restart_on_error: bool | None = None,
        wait_for_finish: int | None = None,
        webhooks: WebhooksList | None = None,
        timeout: Timeout = 'medium',
    ) -> Run:
        """Start the task and immediately return the Run object.

        https://docs.apify.com/api/v2#/reference/actor-tasks/run-collection/run-task

        Args:
            task_input: Task input dictionary.
            build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
                the run uses the build specified in the task settings (typically latest).
            max_items: Maximum number of results that will be returned by this run. If the Actor is charged
                per result, you will not be charged for more results than the given limit.
            memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
                in the task settings.
            run_timeout: Optional timeout for the run. By default, the run uses timeout specified
                in the task settings.
            restart_on_error: If true, the Task run process will be restarted whenever it exits with
                a non-zero status code.
            wait_for_finish: The maximum number of seconds the server waits for the run to finish. By default,
                it is 0, the maximum value is 60.
            webhooks: Optional ad-hoc webhooks (https://docs.apify.com/webhooks/ad-hoc-webhooks) associated with
                the Actor run which can be used to receive a notification, e.g. when the Actor finished or failed.
                If you already have a webhook set up for the Actor or task, you do not have to add it again here.
                Each webhook is represented by a dictionary containing these items:
                    * `event_types`: List of `WebhookEventType` values which trigger the webhook.
                    * `request_url`: URL to which to send the webhook HTTP request.
                    * `payload_template`: Optional template for the request payload.
            timeout: Timeout for the API HTTP request.

        Returns:
            The run object.
        """
        if task_input is not None and not isinstance(task_input, TaskInput):
            task_input = TaskInput.model_validate(task_input)

        request_params = self._build_params(
            build=build,
            maxItems=max_items,
            memory=memory_mbytes,
            timeout=to_seconds(run_timeout, as_int=True),
            restartOnError=restart_on_error,
            waitForFinish=wait_for_finish,
            webhooks=encode_webhooks_to_base64(webhooks),
        )

        response = self._http_client.call(
            url=self._build_url('runs'),
            method='POST',
            headers={'content-type': 'application/json; charset=utf-8'},
            json=task_input.model_dump() if task_input is not None else None,
            params=request_params,
            timeout=timeout,
        )

        result = response_to_dict(response)
        return RunResponse.model_validate(result).data

    def call(
        self,
        *,
        task_input: TaskInputDict | TaskInput | None = None,
        build: str | None = None,
        max_items: int | None = None,
        memory_mbytes: int | None = None,
        run_timeout: timedelta | None = None,
        restart_on_error: bool | None = None,
        webhooks: WebhooksList | None = None,
        wait_duration: timedelta | None = None,
        timeout: Timeout = 'no_timeout',
    ) -> Run | None:
        """Start a task and wait for it to finish before returning the Run object.

        It waits indefinitely, unless the wait_duration argument is provided.

        https://docs.apify.com/api/v2#/reference/actor-tasks/run-collection/run-task

        Args:
            task_input: Task input dictionary.
            build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
                the run uses the build specified in the task settings (typically latest).
            max_items: Maximum number of results that will be returned by this run. If the Actor is charged per result,
                you will not be charged for more results than the given limit.
            memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
                in the task settings.
            run_timeout: Optional timeout for the run. By default, the run uses timeout specified
                in the task settings.
            restart_on_error: If true, the Task run process will be restarted whenever it exits with
                a non-zero status code.
            webhooks: Specifies optional webhooks associated with the Actor run, which can be used to receive
                a notification e.g. when the Actor finished or failed. Note: if you already have a webhook set up for
                the Actor or task, you do not have to add it again here.
            wait_duration: The maximum time the server waits for the task run to finish. If not provided,
                waits indefinitely.
            timeout: Timeout for the API HTTP request.

        Returns:
            The run object.
        """
        started_run = self.start(
            task_input=task_input,
            build=build,
            max_items=max_items,
            memory_mbytes=memory_mbytes,
            run_timeout=run_timeout,
            restart_on_error=restart_on_error,
            webhooks=webhooks,
            timeout=timeout,
        )

        run_client = self._client_registry.run_client(
            resource_id=started_run.id,
            base_url=self._base_url,
            public_base_url=self._public_base_url,
            http_client=self._http_client,
            client_registry=self._client_registry,
        )
        return run_client.wait_for_finish(wait_duration=wait_duration)

    def get_input(self, *, timeout: Timeout = 'short') -> dict:
        """Retrieve the default input for this task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-input-object/get-task-input

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            Retrieved task input.

        Raises:
            NotFoundError: If the task does not exist.
        """
        response = self._http_client.call(
            url=self._build_url('input'),
            method='GET',
            params=self._build_params(),
            timeout=timeout,
        )
        return response_to_dict(response)

    def update_input(self, *, task_input: TaskInputDict | TaskInput, timeout: Timeout = 'short') -> dict:
        """Update the default input for this task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-input-object/update-task-input

        Args:
            task_input: The new default input for this task.
            timeout: Timeout for the API HTTP request.

        Returns:
            The updated task input.
        """
        if task_input is not None and not isinstance(task_input, TaskInput):
            task_input = TaskInput.model_validate(task_input)

        response = self._http_client.call(
            url=self._build_url('input'),
            method='PUT',
            params=self._build_params(),
            json=task_input.model_dump(),
            timeout=timeout,
        )
        return response_to_dict(response)

    def runs(self) -> RunCollectionClient:
        """Retrieve a client for the runs of this task."""
        return self._client_registry.run_collection_client(
            resource_path='runs',
            **self._base_client_kwargs,
        )

    def last_run(self, *, status: ActorJobStatus | None = None, origin: RunOrigin | None = None) -> RunClient:
        """Retrieve the client for the last run of this task.

        Last run is retrieved based on the start time of the runs.

        Args:
            status: Consider only runs with this status.
            origin: Consider only runs started with this origin.

        Returns:
            The resource client for the last run of this task.
        """
        return self._client_registry.run_client(
            resource_id='last',
            resource_path='runs',
            params=self._build_params(status=status, origin=origin),
            **self._base_client_kwargs,
        )

    def webhooks(self) -> WebhookCollectionClient:
        """Retrieve a client for webhooks associated with this task."""
        return self._client_registry.webhook_collection_client(**self._base_client_kwargs)


@docs_group('Resource clients')
class TaskClientAsync(ResourceClientAsync):
    """Sub-client for managing a specific task.

    Provides methods to manage a specific task, e.g. update it, delete it, or start runs. Obtain an instance via an
    appropriate method on the `ApifyClientAsync` class.
    """

    def __init__(
        self,
        *,
        resource_id: str,
        resource_path: str = 'actor-tasks',
        **kwargs: Any,
    ) -> None:
        super().__init__(resource_id=resource_id, resource_path=resource_path, **kwargs)

    async def get(self, *, timeout: Timeout = 'short') -> Task | None:
        """Retrieve the task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/get-task

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            The retrieved task.
        """
        result = await self._get(timeout=timeout)
        if result is None:
            return None
        return TaskResponse.model_validate(result).data

    async def update(
        self,
        *,
        name: str | None = None,
        task_input: TaskInputDict | TaskInput | None = None,
        build: str | None = None,
        max_items: int | None = None,
        memory_mbytes: int | None = None,
        run_timeout: timedelta | None = None,
        restart_on_error: bool | None = None,
        title: str | None = None,
        actor_standby_desired_requests_per_actor_run: int | None = None,
        actor_standby_max_requests_per_actor_run: int | None = None,
        actor_standby_idle_timeout: timedelta | None = None,
        actor_standby_build: str | None = None,
        actor_standby_memory_mbytes: int | None = None,
        is_public: bool | None = None,
        public_config_seo_title: str | None = None,
        public_config_seo_description: str | None = None,
        public_config_input_schema_fields: list[str] | None = None,
        public_config_dataset_name: str | None = None,
        public_config_dataset_view: str | None = None,
        timeout: Timeout = 'short',
    ) -> Task:
        """Update the task with specified fields.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/update-task

        Args:
            name: Name of the task.
            build: Actor build to run. It can be either a build tag or build number. By default, the run uses
                the build specified in the task settings (typically latest).
            max_items: Maximum number of results that will be returned by this run. If the Actor is charged per result,
                you will not be charged for more results than the given limit.
            memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
                in the task settings.
            run_timeout: Optional timeout for the run. By default, the run uses timeout specified
                in the task settings.
            restart_on_error: If true, the Task run process will be restarted whenever it exits with
                a non-zero status code.
            task_input: Task input dictionary.
            title: A human-friendly equivalent of the name.
            actor_standby_desired_requests_per_actor_run: The desired number of concurrent HTTP requests for
                a single Actor Standby run.
            actor_standby_max_requests_per_actor_run: The maximum number of concurrent HTTP requests for
                a single Actor Standby run.
            actor_standby_idle_timeout: If the Actor run does not receive any requests for this time,
                it will be shut down.
            actor_standby_build: The build tag or number to run when the Actor is in Standby mode.
            actor_standby_memory_mbytes: The memory in megabytes to use when the Actor is in Standby mode.
            is_public: Set to `True` to publish the task on its public landing page, or `False` to unpublish it.
                Passing the value the task already has does nothing. Publishing requires the public display
                configuration to be filled in, and write access to the task's Actor.
            public_config_seo_title: SEO title of the public task page. Defaults to the task title when not set.
            public_config_seo_description: SEO description of the public task page. Defaults to the task description
                when not set.
            public_config_input_schema_fields: Names of the task input fields displayed on the public task page.
            public_config_dataset_name: Name of the dataset from the Actor's dataset schema whose results are
                displayed on the public task page.
            public_config_dataset_view: View key from the Actor's dataset schema shown on the public task page.
            timeout: Timeout for the API HTTP request.

        Returns:
            The updated task.
        """
        if task_input is not None and not isinstance(task_input, TaskInput):
            task_input = TaskInput.model_validate(task_input)

        task_fields = UpdateTaskRequest(
            name=name,
            title=title,
            input=task_input,
            is_public=is_public,
            public_config=TaskPublicConfig(
                seo_title=public_config_seo_title,
                seo_description=public_config_seo_description,
                input_schema_fields=public_config_input_schema_fields,
                dataset_name=public_config_dataset_name,
                dataset_view=public_config_dataset_view,
            ),
            options=TaskOptions(
                build=build,
                max_items=max_items,
                memory_mbytes=memory_mbytes,
                timeout_secs=to_seconds(run_timeout, as_int=True),
                restart_on_error=restart_on_error,
            ),
            actor_standby=ActorStandby(
                desired_requests_per_actor_run=actor_standby_desired_requests_per_actor_run,
                max_requests_per_actor_run=actor_standby_max_requests_per_actor_run,
                idle_timeout_secs=to_seconds(actor_standby_idle_timeout, as_int=True),
                build=actor_standby_build,
                memory_mbytes=actor_standby_memory_mbytes,
            ),
        )
        result = await self._update(timeout=timeout, **task_fields.model_dump(by_alias=True, exclude_none=True))
        return TaskResponse.model_validate(result).data

    async def publish(self, *, timeout: Timeout = 'short') -> Task:
        """Publish the task on its public landing page.

        Convenience wrapper over `update` with `is_public` set to `True`. The task's Actor must be public and
        the task must have its public display configuration set up. Requires write access to the task and to its
        Actor. Publishing an already published task does nothing.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/update-task

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            The published task.
        """
        return await self.update(is_public=True, timeout=timeout)

    async def unpublish(self, *, timeout: Timeout = 'short') -> Task:
        """Unpublish the task from its public landing page.

        Convenience wrapper over `update` with `is_public` set to `False`. The public display configuration is
        preserved, so the task can be published again without re-entering it. Requires write access to the task
        and to its Actor. Unpublishing a task that is not published does nothing.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/update-task

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            The unpublished task.
        """
        return await self.update(is_public=False, timeout=timeout)

    async def delete(self, *, timeout: Timeout = 'short') -> None:
        """Delete the task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-object/delete-task

        Args:
            timeout: Timeout for the API HTTP request.
        """
        await self._delete(timeout=timeout)

    async def start(
        self,
        *,
        task_input: TaskInputDict | TaskInput | None = None,
        build: str | None = None,
        max_items: int | None = None,
        memory_mbytes: int | None = None,
        run_timeout: timedelta | None = None,
        restart_on_error: bool | None = None,
        wait_for_finish: int | None = None,
        webhooks: WebhooksList | None = None,
        timeout: Timeout = 'medium',
    ) -> Run:
        """Start the task and immediately return the Run object.

        https://docs.apify.com/api/v2#/reference/actor-tasks/run-collection/run-task

        Args:
            task_input: Task input dictionary.
            build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
                the run uses the build specified in the task settings (typically latest).
            max_items: Maximum number of results that will be returned by this run. If the Actor is charged
                per result, you will not be charged for more results than the given limit.
            memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
                in the task settings.
            run_timeout: Optional timeout for the run. By default, the run uses timeout specified
                in the task settings.
            restart_on_error: If true, the Task run process will be restarted whenever it exits with
                a non-zero status code.
            wait_for_finish: The maximum number of seconds the server waits for the run to finish. By default,
                it is 0, the maximum value is 60.
            webhooks: Optional ad-hoc webhooks (https://docs.apify.com/webhooks/ad-hoc-webhooks) associated with
                the Actor run which can be used to receive a notification, e.g. when the Actor finished or failed.
                If you already have a webhook set up for the Actor or task, you do not have to add it again here.
                Each webhook is represented by a dictionary containing these items:
                    * `event_types`: List of `WebhookEventType` values which trigger the webhook.
                    * `request_url`: URL to which to send the webhook HTTP request.
                    * `payload_template`: Optional template for the request payload.
            timeout: Timeout for the API HTTP request.

        Returns:
            The run object.
        """
        if task_input is not None and not isinstance(task_input, TaskInput):
            task_input = TaskInput.model_validate(task_input)

        request_params = self._build_params(
            build=build,
            maxItems=max_items,
            memory=memory_mbytes,
            timeout=to_seconds(run_timeout, as_int=True),
            restartOnError=restart_on_error,
            waitForFinish=wait_for_finish,
            webhooks=encode_webhooks_to_base64(webhooks),
        )

        response = await self._http_client.call(
            url=self._build_url('runs'),
            method='POST',
            headers={'content-type': 'application/json; charset=utf-8'},
            json=task_input.model_dump() if task_input is not None else None,
            params=request_params,
            timeout=timeout,
        )

        result = response_to_dict(response)
        return RunResponse.model_validate(result).data

    async def call(
        self,
        *,
        task_input: TaskInputDict | TaskInput | None = None,
        build: str | None = None,
        max_items: int | None = None,
        memory_mbytes: int | None = None,
        run_timeout: timedelta | None = None,
        restart_on_error: bool | None = None,
        webhooks: WebhooksList | None = None,
        wait_duration: timedelta | None = None,
        timeout: Timeout = 'no_timeout',
    ) -> Run | None:
        """Start a task and wait for it to finish before returning the Run object.

        It waits indefinitely, unless the wait_duration argument is provided.

        https://docs.apify.com/api/v2#/reference/actor-tasks/run-collection/run-task

        Args:
            task_input: Task input dictionary.
            build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
                the run uses the build specified in the task settings (typically latest).
            max_items: Maximum number of results that will be returned by this run. If the Actor is charged per result,
                you will not be charged for more results than the given limit.
            memory_mbytes: Memory limit for the run, in megabytes. By default, the run uses a memory limit specified
                in the task settings.
            run_timeout: Optional timeout for the run. By default, the run uses timeout specified
                in the task settings.
            restart_on_error: If true, the Task run process will be restarted whenever it exits with
                a non-zero status code.
            webhooks: Specifies optional webhooks associated with the Actor run, which can be used to receive
                a notification e.g. when the Actor finished or failed. Note: if you already have a webhook set up for
                the Actor or task, you do not have to add it again here.
            wait_duration: The maximum time the server waits for the task run to finish. If not provided,
                waits indefinitely.
            timeout: Timeout for the API HTTP request.

        Returns:
            The run object.
        """
        started_run = await self.start(
            task_input=task_input,
            build=build,
            max_items=max_items,
            memory_mbytes=memory_mbytes,
            run_timeout=run_timeout,
            restart_on_error=restart_on_error,
            webhooks=webhooks,
            timeout=timeout,
        )
        run_client = self._client_registry.run_client(
            resource_id=started_run.id,
            base_url=self._base_url,
            public_base_url=self._public_base_url,
            http_client=self._http_client,
            client_registry=self._client_registry,
        )
        return await run_client.wait_for_finish(wait_duration=wait_duration)

    async def get_input(self, *, timeout: Timeout = 'short') -> dict:
        """Retrieve the default input for this task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-input-object/get-task-input

        Args:
            timeout: Timeout for the API HTTP request.

        Returns:
            Retrieved task input.

        Raises:
            NotFoundError: If the task does not exist.
        """
        response = await self._http_client.call(
            url=self._build_url('input'),
            method='GET',
            params=self._build_params(),
            timeout=timeout,
        )
        return response_to_dict(response)

    async def update_input(self, *, task_input: TaskInputDict | TaskInput, timeout: Timeout = 'short') -> dict:
        """Update the default input for this task.

        https://docs.apify.com/api/v2#/reference/actor-tasks/task-input-object/update-task-input

        Args:
            task_input: The new default input for this task.
            timeout: Timeout for the API HTTP request.

        Returns:
            The updated task input.
        """
        if task_input is not None and not isinstance(task_input, TaskInput):
            task_input = TaskInput.model_validate(task_input)

        response = await self._http_client.call(
            url=self._build_url('input'),
            method='PUT',
            params=self._build_params(),
            json=task_input.model_dump(),
            timeout=timeout,
        )
        return response_to_dict(response)

    def runs(self) -> RunCollectionClientAsync:
        """Retrieve a client for the runs of this task."""
        return self._client_registry.run_collection_client(
            resource_path='runs',
            **self._base_client_kwargs,
        )

    def last_run(self, *, status: ActorJobStatus | None = None, origin: RunOrigin | None = None) -> RunClientAsync:
        """Retrieve the client for the last run of this task.

        Last run is retrieved based on the start time of the runs.

        Args:
            status: Consider only runs with this status.
            origin: Consider only runs started with this origin.

        Returns:
            The resource client for the last run of this task.
        """
        return self._client_registry.run_client(
            resource_id='last',
            resource_path='runs',
            params=self._build_params(status=status, origin=origin),
            **self._base_client_kwargs,
        )

    def webhooks(self) -> WebhookCollectionClientAsync:
        """Retrieve a client for webhooks associated with this task."""
        return self._client_registry.webhook_collection_client(**self._base_client_kwargs)
