"""Deploy handlers for the new platform-based deployment flow.

The CLI no longer executes Helm commands directly. Instead:
1. CLI builds and pushes Docker images to platform-managed registry
2. CLI calls platform API to trigger deployment
3. Platform (nucleus) handles all Helm/K8s operations
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field
from python_on_whales import docker
from rich.console import Console

from terminaluse.lib.cli.utils.exceptions import DeploymentError
from terminaluse.lib.sdk.config.deployment_config import EnvironmentDeploymentConfig
from terminaluse.lib.utils.logging import make_logger

if TYPE_CHECKING:
    from terminaluse.lib.client import TerminalUse

logger = make_logger(__name__)
console = Console()

# Deployment polling configuration
DEFAULT_DEPLOY_TIMEOUT = 300  # 5 minutes
DEFAULT_POLL_INTERVAL = 2  # seconds


class DeployConfig(BaseModel):
    """Configuration for platform deployment API."""

    replicas: int = Field(default=1, description="Number of replicas")
    resources: dict[str, Any] = Field(default_factory=dict, description="Resource requests/limits")


def docker_login(registry: str, username: str, password: str) -> None:
    """Login to Docker registry using platform-provided credentials.

    Args:
        registry: Registry URL (e.g., "us-east4-docker.pkg.dev")
        username: Registry username (typically "oauth2accesstoken")
        password: Registry token/password
    """
    import subprocess

    try:
        # Use subprocess to suppress "Login Succeeded" message
        subprocess.run(
            ["docker", "login", registry, "-u", username, "--password-stdin"],
            input=password,
            capture_output=True,
            text=True,
            check=True,
        )
        logger.info(f"Successfully logged in to registry: {registry}")
    except subprocess.CalledProcessError as e:
        raise DeploymentError(f"Failed to login to registry {registry}: {e.stderr}") from e
    except Exception as e:
        raise DeploymentError(f"Failed to login to registry {registry}: {e}") from e


def docker_logout(registry: str) -> None:
    """Logout from Docker registry.

    Args:
        registry: Registry URL to logout from
    """
    import subprocess

    try:
        # Use subprocess directly to suppress docker CLI output
        subprocess.run(
            ["docker", "logout", registry],
            capture_output=True,
            check=True,
        )
    except Exception as e:
        logger.warning(f"Failed to logout from registry {registry}: {e}")


def _format_deploy_event(event: Any) -> tuple[str, str]:
    """Format a deploy stream event into a user-friendly message.

    Args:
        event: The deploy stream event object

    Returns:
        Tuple of (status_symbol, message) for display
    """
    event_type = event.type

    # Map event types to user-friendly messages (no emojis, use ✓ for done)
    event_messages = {
        "connected": ("•", "Connected to deployment stream"),
        "queued": ("•", "Deployment queued"),
        "helm_started": ("•", "Starting Helm deployment"),
        "helm_waiting": ("•", "Waiting for pods to be ready"),
        "ready": ("✓", "Deployment successful"),
        "failed": ("✗", "Deployment failed"),
        "rolling_back": ("•", "Rolling back"),
        "complete": ("✓", "Deployment complete"),
        "error": ("✗", "Stream error"),
    }

    symbol, message = event_messages.get(event_type, ("•", event_type.replace("_", " ").title()))

    # Add context from event fields if available
    if hasattr(event, "message") and event.message:
        message = f"{message}: {event.message}"
    if hasattr(event, "failure_reason") and event.failure_reason:
        message = f"{message} ({event.failure_reason})"
    if hasattr(event, "status") and event.status:
        message = f"{message} (status: {event.status})"

    return symbol, message


def stream_deployment_status(
    client: "TerminalUse",
    version_id: str,
    branch_id: str,
    timeout: int = DEFAULT_DEPLOY_TIMEOUT,
) -> Any:
    """Stream deployment events and show real-time progress.

    Uses SSE streaming to get real-time deployment events.
    Falls back to polling if streaming fails.

    Args:
        client: TerminalUse client instance
        version_id: ID of the version being deployed
        branch_id: ID of the branch (for fallback polling)
        timeout: Maximum time to wait in seconds (default: 300)

    Returns:
        Final branch status object

    Raises:
        DeploymentError: If deployment times out or fails
    """
    from rich.status import Status

    # Terminal event types that signal deployment is done
    terminal_event_types = {"ready", "failed", "complete", "error"}
    start = time.time()

    try:
        with Status("[bold blue]Deploying...", console=console) as status:
            for event in client.versions.deploy_stream(version_id=version_id):
                # Check timeout
                if time.time() - start > timeout:
                    raise DeploymentError(f"Deployment timed out after {timeout} seconds")

                event_type = event.type
                symbol, message = _format_deploy_event(event)

                # Update status display based on event type
                if event_type in terminal_event_types:
                    # Terminal event - show final status and exit
                    if event_type == "ready":
                        console.print(f"[green]{symbol}[/green] {message}")
                    elif event_type == "complete":
                        # "complete" event has a status field indicating final state
                        event_status = getattr(event, "status", "").lower()
                        if event_status == "active":
                            console.print(f"[green]{symbol}[/green] {message}")
                        else:
                            console.print(f"[red]✗[/red] Deployment ended with status: {event_status}")
                    elif event_type in ("failed", "error"):
                        console.print(f"[red]{symbol}[/red] {message}")

                    # Get final branch status
                    return client.branches.retrieve(branch_id=branch_id)
                else:
                    # In-progress event - update spinner
                    status.update(f"[bold blue]{symbol}[/bold blue] {message}")
                    # Print milestone events
                    if event_type == "helm_started":
                        console.print(f"[green]{symbol}[/green] {message}")

        # Stream ended without terminal event - fall back to polling
        logger.warning("SSE stream ended without terminal event, falling back to polling")
        return poll_deployment_status(client, branch_id, timeout - int(time.time() - start))

    except GeneratorExit:
        # Stream was interrupted
        raise DeploymentError("Deployment stream interrupted")
    except DeploymentError:
        raise
    except Exception as e:
        # SSE streaming failed - fall back to polling
        logger.warning(f"SSE streaming failed ({e}), falling back to polling")
        remaining_timeout = max(10, timeout - int(time.time() - start))
        return poll_deployment_status(client, branch_id, remaining_timeout)


def poll_deployment_status(
    client: "TerminalUse",
    branch_id: str,
    timeout: int = DEFAULT_DEPLOY_TIMEOUT,
    interval: int = DEFAULT_POLL_INTERVAL,
) -> Any:
    """Poll branch status until ready, failed, or timeout.

    Uses auto-generated client.branches.retrieve() API.

    Args:
        client: TerminalUse client instance
        branch_id: ID of the branch to poll
        timeout: Maximum time to wait in seconds (default: 300)
        interval: Polling interval in seconds (default: 2)

    Returns:
        Final branch status object

    Raises:
        DeploymentError: If deployment times out
    """
    from rich.status import Status

    # Terminal states that indicate deployment is done (success or failure)
    # These are VersionStatus values (from current_version.status)
    terminal_states = {"active", "failed", "unhealthy", "retired", "rolled_back"}

    start = time.time()
    last_status = None

    with Status("[bold blue]Waiting for deployment...", console=console) as status:
        while time.time() - start < timeout:
            try:
                branch = client.branches.retrieve(branch_id)
                # Get status from current_version (Version now holds the status)
                if branch.current_version:
                    current_status = branch.current_version.status.lower()
                else:
                    current_status = "deploying"

                # Update status display if changed
                if current_status != last_status:
                    status.update(f"[bold blue]⠋[/bold blue] Status: {current_status}")
                    last_status = current_status

                if current_status in terminal_states:
                    return branch

                time.sleep(interval)

            except Exception as e:
                logger.warning(f"Error polling deployment status: {e}")
                status.update("[yellow]⚠[/yellow] Retrying...")
                time.sleep(interval)

    raise DeploymentError(f"Deployment timed out after {timeout} seconds (last status: {last_status})")


def build_deploy_config(env_config: EnvironmentDeploymentConfig, is_production: bool = False) -> dict[str, Any]:
    """Build deployment configuration from resolved environment config.

    Args:
        env_config: Environment-specific deployment config (production or preview)
        is_production: Whether this is a production environment (affects are_tasks_sticky default)

    Returns:
        Configuration dict for POST /agents/deploy API
    """
    # Resolve are_tasks_sticky default based on environment type
    are_tasks_sticky = env_config.areTasksSticky
    if are_tasks_sticky is None:
        are_tasks_sticky = is_production  # Production defaults to True, preview to False

    return {
        "replicas": env_config.replicaCount,
        "resources": {
            "requests": {
                "cpu": env_config.resources.requests.cpu,
                "memory": env_config.resources.requests.memory,
            },
            "limits": {
                "cpu": env_config.resources.limits.cpu,
                "memory": env_config.resources.limits.memory,
            },
        },
        "are_tasks_sticky": are_tasks_sticky,
    }


def validate_docker_running() -> bool:
    """Check if Docker daemon is running.

    Returns:
        True if Docker is available and running
    """
    try:
        docker.info()
        return True
    except Exception:
        return False


def check_dockerfile_exists(manifest_path: str) -> bool:
    """Check if Dockerfile exists in the manifest directory.

    Args:
        manifest_path: Path to the manifest file

    Returns:
        True if Dockerfile exists
    """
    from pathlib import Path

    manifest_dir = Path(manifest_path).parent
    dockerfile_path = manifest_dir / "Dockerfile"
    return dockerfile_path.exists()
