"""Execution handle for webhook (async) execution mode."""

from __future__ import annotations

import time

import httpx

from baponi._base import build_cancel_url, build_execution_url, raise_for_status
from baponi._constants import DEFAULT_POLL_INTERVAL, TERMINAL_STATUSES
from baponi.types import CancelResponse, ExecutionStatus


class ExecutionHandle:
    """Handle for a webhook-mode execution.

    Returned by ``client.execute_webhook()``. Provides methods to poll
    for status, wait until completion, or cancel the execution.

    Usage::

        handle = client.execute_webhook("import time; time.sleep(5)")
        print(handle.trace_id)
        status = handle.wait()
        print(status.status)  # "success"
    """

    def __init__(
        self,
        trace_id: str,
        *,
        base_url: str,
        headers: dict[str, str],
        client: httpx.Client,
    ) -> None:
        self._trace_id = trace_id
        self._base_url = base_url
        self._headers = headers
        self._client = client

    @property
    def trace_id(self) -> str:
        return self._trace_id

    def poll(self) -> ExecutionStatus:
        """GET the current execution status."""
        url = build_execution_url(self._base_url, self._trace_id)
        response = self._client.get(url, headers=self._headers)
        raise_for_status(response)
        return ExecutionStatus.model_validate(response.json())

    def cancel(self) -> CancelResponse:
        """Cancel the running execution."""
        url = build_cancel_url(self._base_url, self._trace_id)
        response = self._client.post(url, headers=self._headers)
        raise_for_status(response)
        return CancelResponse.model_validate(response.json())

    def wait(
        self,
        *,
        poll_interval: float = DEFAULT_POLL_INTERVAL,
        timeout: float | None = None,
    ) -> ExecutionStatus:
        """Poll until execution reaches a terminal status.

        Uses exponential backoff: doubles poll_interval each iteration,
        capped at 10 seconds.

        Args:
            poll_interval: Initial seconds between poll requests.
            timeout: Maximum seconds to wait. None means wait forever.

        Returns:
            Final ExecutionStatus.

        Raises:
            TimeoutError: If timeout is exceeded.
        """
        start = time.monotonic()
        current_interval = poll_interval
        while True:
            if timeout is not None and (time.monotonic() - start) > timeout:
                raise TimeoutError(
                    f"Execution {self._trace_id} did not complete within {timeout}s"
                )
            status = self.poll()
            if status.status in TERMINAL_STATUSES:
                return status
            time.sleep(current_interval)
            current_interval = min(current_interval * 2, 10.0)


class AsyncExecutionHandle:
    """Async handle for a webhook-mode execution.

    Returned by ``client.execute_webhook()``. Provides async methods to
    poll for status, wait until completion, or cancel the execution.
    """

    def __init__(
        self,
        trace_id: str,
        *,
        base_url: str,
        headers: dict[str, str],
        client: httpx.AsyncClient,
    ) -> None:
        self._trace_id = trace_id
        self._base_url = base_url
        self._headers = headers
        self._client = client

    @property
    def trace_id(self) -> str:
        return self._trace_id

    async def poll(self) -> ExecutionStatus:
        """GET the current execution status."""
        url = build_execution_url(self._base_url, self._trace_id)
        response = await self._client.get(url, headers=self._headers)
        raise_for_status(response)
        return ExecutionStatus.model_validate(response.json())

    async def cancel(self) -> CancelResponse:
        """Cancel the running execution."""
        url = build_cancel_url(self._base_url, self._trace_id)
        response = await self._client.post(url, headers=self._headers)
        raise_for_status(response)
        return CancelResponse.model_validate(response.json())

    async def wait(
        self,
        *,
        poll_interval: float = DEFAULT_POLL_INTERVAL,
        timeout: float | None = None,
    ) -> ExecutionStatus:
        """Poll until execution reaches a terminal status.

        Uses exponential backoff: doubles poll_interval each iteration,
        capped at 10 seconds.
        """
        import asyncio

        start = time.monotonic()
        current_interval = poll_interval
        while True:
            if timeout is not None and (time.monotonic() - start) > timeout:
                raise TimeoutError(
                    f"Execution {self._trace_id} did not complete within {timeout}s"
                )
            status = await self.poll()
            if status.status in TERMINAL_STATUSES:
                return status
            await asyncio.sleep(current_interval)
            current_interval = min(current_interval * 2, 10.0)
