import asyncio
import time
from uuid import UUID

import typer

from cli.commands.executions.get.presenter import (
    present_execution,
    watch_execution_live,
)
from cli.commands.flag_utils import validate_uuid
from cli.infrastructure.command import async_command, authenticated_client
from cli.infrastructure.output import OutputFormat, OutputService


_POLL_INTERVAL_SECONDS = 3.0


def executions_get_command(
    execution_id: str = typer.Argument(..., help="Execution UUID"),
    watch: bool = typer.Option(
        False, "--watch", help="Poll until execution reaches a terminal state"
    ),
    timeout: float | None = typer.Option(
        None,
        "--timeout",
        help="Maximum seconds to wait when using --watch (default: wait indefinitely)",
    ),
    full_output: bool = typer.Option(
        False,
        "--full-output",
        help="Show full tool outputs instead of compact truncation",
    ),
) -> None:
    _execute_get(
        execution_id=validate_uuid(execution_id, "execution"),
        watch=watch,
        timeout=timeout,
        full_output=full_output,
    )


@async_command
async def _execute_get(
    execution_id: UUID,
    watch: bool,
    timeout: float | None,
    full_output: bool,
) -> None:
    async with authenticated_client() as (_config, client):
        if not watch:
            response = await client.get_execution(execution_id)
            present_execution(response, full_output=full_output)
            return

        output = OutputService.get()
        start_time = time.monotonic() if timeout is not None else None

        if output.format == OutputFormat.JSON:
            last_response = await client.get_execution(execution_id)
            while not last_response.is_terminal and not _watch_timed_out(
                start_time=start_time,
                timeout=timeout,
            ):
                await asyncio.sleep(_POLL_INTERVAL_SECONDS)
                last_response = await client.get_execution(execution_id)

            if not last_response.is_terminal:
                output.progress(f"Watch timed out after {timeout:.0f}s.")
            output.raw(last_response)
            return

        await watch_execution_live(
            client=client,
            execution_id=execution_id,
            timeout=timeout,
            full_output=full_output,
        )


def _watch_timed_out(start_time: float | None, timeout: float | None) -> bool:
    if timeout is None:
        return False
    if start_time is None:
        raise RuntimeError("watch timeout requires a start time")
    return time.monotonic() - start_time >= timeout
