"""Deployment wrapper for tracking job status and logs."""

from __future__ import annotations

import time
from typing import Any

from rich.console import Console
from rich.live import Live
from rich.table import Table
from rich.text import Text

from velar.client import DeploymentPollError, DeploymentPollTimeout, VelarClient

console = Console()

# Terminal deployment states that will never transition further.
_TERMINAL_STATES = frozenset({"completed", "failed", "cancelled", "error"})


class Deployment:
    """A handle to a running or pending Velar deployment.

    Provides methods to check status, stream logs, wait for readiness,
    and cancel.

    Usage::

        app = velar.App("my-app")
        results = app.deploy()
        deploy = results["my_function"]  # Deployment instance
        deploy.wait()                     # block until running
        print(deploy.status())            # "running"
        deploy.logs()                     # print logs
        deploy.cancel()                   # cancel deployment
    """

    def __init__(
        self,
        *,
        deployment_id: str,
        name: str,
        data: dict[str, Any],
        client: VelarClient,
    ):
        self.id = deployment_id
        self.name = name
        self._data = data
        self._client = client

    @property
    def endpoint_url(self) -> str | None:
        return self._data.get("endpoint_url")

    def status(self) -> str:
        """Fetch current deployment status from the API."""
        d = self._client.get_deployment(self.id)
        self._data = d
        return d.get("status", "unknown")

    def refresh(self) -> dict[str, Any]:
        """Refresh and return the full deployment data."""
        self._data = self._client.get_deployment(self.id)
        return self._data

    def logs(self) -> dict[str, Any]:
        """Fetch logs and pod status from the API.

        Returns a dict with keys: deployment_id, status, logs, pod (if available).
        """
        return self._client.get_deployment_logs(self.id)

    def wait(self, *, timeout: float = 300, interval: float = 3, verbose: bool = True) -> str:
        """Block until the deployment reaches 'running' or a terminal state.

        Args:
            timeout: Maximum seconds to wait.
            interval: Seconds between polls.
            verbose: If True, show a live spinner with status updates.

        Returns:
            The final deployment status.

        Raises:
            DeploymentPollTimeout: If the timeout is exceeded.
            DeploymentPollError: If the deployment reaches a terminal failure state.
        """
        if not verbose:
            d = self._client.poll_deployment(self.id, timeout=timeout, interval=interval)
            self._data = d
            return d.get("status", "running")

        deadline = time.monotonic() + timeout
        last_status = ""

        with Live(console=console, refresh_per_second=4, transient=True) as live:
            while True:
                d = self._client.get_deployment(self.id)
                self._data = d
                status = d.get("status", "unknown")

                if status != last_status:
                    last_status = status

                # Build status line
                status_colors = {
                    "pending": "yellow",
                    "provisioning": "yellow",
                    "running": "green",
                    "completed": "blue",
                    "failed": "red",
                    "cancelled": "dim",
                }
                color = status_colors.get(status, "white")
                elapsed = int(time.monotonic() - (deadline - timeout))

                live.update(
                    Text.assemble(
                        ("  ", ""),
                        (f"[{self.id[:8]}] ", "cyan"),
                        (f"{self.name}", "bold"),
                        ("  ", ""),
                        (f"{status}", color),
                        ("  ", ""),
                        (f"({elapsed}s)", "dim"),
                    )
                )

                if status == "running":
                    break

                if status in _TERMINAL_STATES:
                    err_msg = d.get("error_message", "")
                    console.print(
                        f"  [{color}]{self.id[:8]} {self.name}: {status}[/{color}]"
                        + (f" - {err_msg}" if err_msg else "")
                    )
                    raise DeploymentPollError(
                        f"Deployment {self.id} reached terminal state '{status}'"
                    )

                if time.monotonic() >= deadline:
                    raise DeploymentPollTimeout(
                        f"Deployment {self.id} did not reach 'running' within {timeout}s "
                        f"(last status: '{status}')"
                    )

                time.sleep(interval)

        invoke_url = f"{self._client._config.invoke_url}/{self.id}"
        console.print(
            f"  [green]\u2713[/green] [cyan]{self.id[:8]}[/cyan] [bold]{self.name}[/bold] is running"
            f"  [dim]{invoke_url}[/dim]"
        )
        return "running"

    def cancel(self) -> dict[str, Any]:
        """Cancel this deployment."""
        result = self._client.cancel_deployment(self.id)
        self._data = result
        return result

    def __repr__(self) -> str:
        return f"<Deployment id={self.id[:8]} name={self.name} status={self._data.get('status', '?')}>"
