from __future__ import annotations

import os
import time
from pathlib import Path
from typing import TYPE_CHECKING

import httpx
from rich.console import Console

from terminaluse.lib.cli.utils.errors import CLIError, ErrorCode, ExitCode

if TYPE_CHECKING:
    from terminaluse.lib.client import TerminalUse

console = Console()


def _get_logger():
    from terminaluse.lib.utils.logging import make_logger

    return make_logger(__name__)


def _is_remote_build_enabled() -> bool:
    """Check if remote build is enabled (opt-in via env var)."""
    raw = os.getenv("TU_DEPLOY_REMOTE_BUILD", "0").strip().lower()
    if raw in ("1", "true", "yes", "on"):
        return True
    return False


def _create_build_context_tar_gz(config_path: str) -> bytes:
    """Create a tar.gz build context using the same manifest filtering as local builds."""
    from terminaluse.lib.sdk.config.agent_manifest import AgentManifest, BuildContextManager

    manifest = AgentManifest.from_yaml(file_path=config_path)
    build_context_root = (Path(config_path).parent / manifest.build.context.root).resolve()

    with manifest.context_manager(build_context_root) as build_ctx:
        assert build_ctx.path is not None
        with BuildContextManager.zipped(root_path=build_ctx.path) as buf:
            data = buf.read()
            if not isinstance(data, (bytes, bytearray)):
                raise RuntimeError("Failed to create build context archive")
            return bytes(data)


def _upload_presigned(url: str, *, data: bytes, content_type: str, timeout_s: float = 600.0) -> None:
    # Presigned URLs should not receive auth headers.
    with httpx.Client(timeout=httpx.Timeout(timeout_s, connect=10.0)) as http_client:
        resp = http_client.put(url, content=data, headers={"Content-Type": content_type})
        resp.raise_for_status()


def remote_build_image(
    *,
    client: TerminalUse,
    namespace: str,
    agent_name: str,
    config_path: str,
    tag: str,
    git_branch: str | None,
    git_hash: str | None,
) -> str:
    """Build/push an image remotely via Cloud Build using the SDK.

    Returns:
        image_url: Full container image URL to pass to /agents/deploy.
    """
    from terminaluse.core.api_error import ApiError

    logger = _get_logger()

    if not _is_remote_build_enabled():
        raise CLIError(
            message="Remote build disabled",
            code=ErrorCode.VALIDATION_ERROR,
            exit_code=ExitCode.USER_ERROR,
            hint="Unset TU_DEPLOY_REMOTE_BUILD or set it to 1 to enable remote builds",
        )

    console.print("\nBuilding image remotely (Cloud Build)...")

    # 1) Create build context archive (tar.gz)
    archive = _create_build_context_tar_gz(config_path)
    size_mb = len(archive) / (1024 * 1024)
    console.print(f"  Context archive: {size_mb:.1f} MiB")

    # 2) Prepare (get presigned upload URL + computed image URL)
    try:
        prepare_resp = client.builds.prepare(
            namespace=namespace,
            agent_name=agent_name,
            tag=tag,
        )
    except ApiError as e:
        if e.status_code == 404:
            # This can mean either:
            # - The endpoint is not deployed
            # - The namespace doesn't exist / access denied
            msg = str(e.body) if e.body else None
            if msg and "namespace" in msg.lower():
                raise CLIError(
                    message=msg,
                    code=ErrorCode.NOT_FOUND,
                    exit_code=ExitCode.NOT_FOUND,
                    hint="Check your agent name/namespace and your access.",
                ) from e

            raise CLIError(
                message="Remote build is not available on this API environment",
                code=ErrorCode.NOT_FOUND,
                exit_code=ExitCode.NOT_FOUND,
                hint=(
                    "Set TERMINALUSE_BASE_URL to an environment that has remote builds deployed, "
                    "or unset TU_DEPLOY_REMOTE_BUILD to use local Docker builds (default)."
                ),
            ) from e
        logger.warning("build_prepare_error status=%s body=%s", e.status_code, e.body)
        raise CLIError(
            message=f"Failed to prepare remote build (HTTP {e.status_code})",
            code=ErrorCode.UNKNOWN_ERROR,
            exit_code=ExitCode.INTERNAL_ERROR,
            hint="Try again. If this persists, contact support.",
        ) from e

    # 3) Upload build context to GCS via presigned URL
    console.print("  Uploading build context...")
    _upload_presigned(
        prepare_resp.upload_url,
        data=archive,
        content_type=prepare_resp.content_type,
        timeout_s=600.0,
    )

    # 4) Submit Cloud Build build
    try:
        submit_resp = client.builds.submit(
            namespace=namespace,
            agent_name=agent_name,
            tag=tag,
            bucket=prepare_resp.bucket,
            object_key=prepare_resp.object_key,
            git_branch=git_branch,
            git_hash=git_hash,
        )
    except ApiError as e:
        logger.warning("build_submit_error status=%s body=%s", e.status_code, e.body)
        raise CLIError(
            message=f"Failed to submit remote build (HTTP {e.status_code})",
            code=ErrorCode.UNKNOWN_ERROR,
            exit_code=ExitCode.INTERNAL_ERROR,
            hint="Try again. If this persists, contact support.",
        ) from e

    build_id = submit_resp.build_id
    log_url = submit_resp.log_url
    image_url = prepare_resp.image_url

    console.print(f"  Build ID: {build_id}")
    if log_url:
        console.print(f"  Logs: {log_url}")

    # 5) Poll status until terminal
    from rich.status import Status

    terminal = {"succeeded", "failed", "canceled", "timed_out"}
    start = time.time()
    poll_timeout_s = 60 * 30  # 30 minutes
    last_status = None
    consecutive_404s = 0
    max_consecutive_404s = 10  # ~15 seconds of 404s before giving up

    with Status("[bold blue]Building remotely...[/bold blue]", console=console) as status:
        while True:
            if time.time() - start > poll_timeout_s:
                raise CLIError(
                    message="Remote build timed out",
                    code=ErrorCode.UNKNOWN_ERROR,
                    exit_code=ExitCode.INTERNAL_ERROR,
                    hint=(
                        f"Check Cloud Build logs: {log_url}" if log_url else "Check Cloud Build logs in GCP console."
                    ),
                )

            try:
                build_status = client.builds.retrieve(build_id)
                consecutive_404s = 0  # Reset on success
            except ApiError as e:
                if e.status_code == 404:
                    consecutive_404s += 1
                    if consecutive_404s >= max_consecutive_404s:
                        raise CLIError(
                            message=f"Build {build_id} not found after {consecutive_404s} retries",
                            code=ErrorCode.NOT_FOUND,
                            exit_code=ExitCode.NOT_FOUND,
                            hint=(
                                f"The build may have failed to register. Check Cloud Build logs: {log_url}"
                                if log_url
                                else "Try again or contact support."
                            ),
                        ) from e
                    # Treat as transient for a short period right after submit.
                    time.sleep(1.5)
                    continue
                raise

            cur = build_status.status or "unknown"
            cloud = build_status.cloud_status
            if cur != last_status:
                suffix = f" ({cloud})" if cloud else ""
                status.update(f"[bold blue]Remote build:[/bold blue] {cur}{suffix}")
                last_status = cur

            if cur in terminal:
                if cur == "succeeded":
                    console.print("[green]✓[/green] Remote build succeeded")
                    return image_url

                failure_message = build_status.failure_message
                detail = f"{cur}"
                if failure_message:
                    detail = f"{detail}: {failure_message}"
                raise CLIError(
                    message=f"Remote build failed ({detail})",
                    code=ErrorCode.UNKNOWN_ERROR,
                    exit_code=ExitCode.INTERNAL_ERROR,
                    hint=(
                        f"Check Cloud Build logs: {log_url}" if log_url else "Check Cloud Build logs in GCP console."
                    ),
                )

            time.sleep(2.0)


def remote_build_enabled() -> bool:
    return _is_remote_build_enabled()
