"""Local build + remote publish orchestration.

Split of the monolithic build_and_publish_to_apollo:
- LOCAL: read Chart.yaml, Docker buildx, package chart (pure Python), OCI push
- REMOTE: generate manifest, publish to Apollo (via cube-cloud)

The user only needs Docker installed (and only for image builds).
"""

from __future__ import annotations

import logging
import os
import tempfile

from cube_common.config import ACR_DOCKER_PREFIX, ACR_HELM_PREFIX, ACR_REGISTRY, APOLLO_GROUP_ID
from cube_common.helm_chart import (
    ChartError,
    bump_version,
    find_chart_yaml,
    package_chart,
    parse_chart,
    update_chart_version,
)
from cube_common.manifest import generate_manifest
from cube_common.oci_registry import OCIPushError, push_chart

from cube_agent.local.docker_build import DockerBuildError, build_and_push, docker_available, docker_login
from cube_agent.remote.client import CloudClient, CloudClientError

logger = logging.getLogger(__name__)


async def build_and_publish(
    chart_path: str | None = None,
    version: str | None = None,
    bump: str = "patch",
    app_version: str | None = None,
    skip_docker: bool = False,
    dry_run: bool = False,
) -> str:
    """Build and publish an app to Apollo.

    Pipeline:
    1. Find and parse Chart.yaml (local, pure Python)
    2. Get ACR token (remote, via cube-cloud)
    3. Docker login + build + push (local, only if Dockerfile exists)
    4. Update Chart.yaml version (local, pure Python)
    5. Package chart (local, pure Python tarfile)
    6. Push chart to OCI registry (local, pure Python httpx)
    7. Publish manifest to Apollo (remote, via cube-cloud)
    """
    steps: list[str] = []
    errors: list[str] = []

    # Step 1: Find and parse Chart.yaml
    try:
        chart_yaml = find_chart_yaml(chart_path)
        info = parse_chart(chart_yaml)
    except ChartError as e:
        return f"Error: {e}"

    chart_dir = info["dir"]
    chart_name = info["name"]
    current_version = info["version"]

    new_version = version or bump_version(current_version, bump)
    new_app_version = app_version or new_version

    # Check for Dockerfile
    dockerfile_path = os.path.join(chart_dir, "Dockerfile")
    if not os.path.isfile(dockerfile_path):
        dockerfile_path = os.path.join(os.path.dirname(chart_dir), "Dockerfile")
    has_dockerfile = os.path.isfile(dockerfile_path) and not skip_docker

    summary = f"""
Helm Deploy Summary
{'=' * 42}
Chart:        {chart_name}
Path:         {chart_yaml}
Version:      {current_version} -> {new_version}
App Version:  {info.get('appVersion', 'N/A')} -> {new_app_version}
Docker Build: {'Yes' if has_dockerfile else 'No (no Dockerfile)'}
{'=' * 42}

Pipeline:
1. Get ACR token (remote)
2. {'Build & push Docker image (local)' if has_dockerfile else '(skip Docker)'}
3. Update Chart.yaml version (local)
4. Package Helm chart (local, pure Python)
5. Push to OCI registry (local, pure Python)
6. Publish to Apollo (remote)
"""

    if dry_run:
        return summary + "\nDRY RUN - No changes made."

    steps.append(summary)

    # Step 2: Get ACR token from cube-cloud
    async with CloudClient() as cloud:
        try:
            token_text = await cloud.call_tool("acr_get_token", {})
            # Extract the token from the response text
            token = _extract_token(token_text)
        except CloudClientError as e:
            return f"Error getting ACR token: {e}"

    # Step 3: Docker build + push (local)
    if has_dockerfile:
        docker_tag = f"{ACR_DOCKER_PREFIX}/{chart_name}:{new_app_version}"
        steps.append(f"Building Docker image: {docker_tag}")

        try:
            await docker_login(ACR_REGISTRY, token)
            result = await build_and_push(
                os.path.dirname(dockerfile_path) or ".",
                docker_tag,
            )
            steps.append(f"[ok] {result}")
        except DockerBuildError as e:
            errors.append(str(e))
            steps.append(f"[warn] Docker build failed (continuing with Helm only): {e}")

    # Step 4: Update Chart.yaml version
    try:
        update_chart_version(
            chart_yaml, new_version, new_app_version if has_dockerfile else None
        )
        steps.append(f"[ok] Updated Chart.yaml to version {new_version}")
    except ChartError as e:
        return f"Error updating Chart.yaml: {e}"

    # Step 5: Package chart (pure Python)
    try:
        tgz_path = package_chart(chart_dir, new_version)
        steps.append(f"[ok] Packaged: {os.path.basename(tgz_path)}")
    except ChartError as e:
        return f"Error packaging chart: {e}"

    # Step 6: Push to OCI registry (pure Python httpx)
    repo_path = f"charts/{APOLLO_GROUP_ID}/{chart_name}"
    try:
        digest = await push_chart(
            tgz_path=tgz_path,
            registry=ACR_REGISTRY,
            repository=repo_path,
            tag=new_version,
            token=token,
        )
        steps.append(f"[ok] Pushed to {ACR_HELM_PREFIX}/{chart_name}:{new_version}")
    except OCIPushError as e:
        return f"Error pushing chart: {e}"
    finally:
        # Clean up .tgz
        try:
            os.remove(tgz_path)
        except OSError:
            pass

    # Step 7: Publish manifest to Apollo (via cube-cloud)
    oci_url = f"{ACR_HELM_PREFIX}/{chart_name}"
    manifest_content = generate_manifest(chart_name, new_version, APOLLO_GROUP_ID, oci_url)

    async with CloudClient() as cloud:
        try:
            publish_result = await cloud.call_tool(
                "apollo_publish_manifest",
                {"manifest_yaml": manifest_content},
            )
            steps.append(f"[ok] Published to Apollo")
        except CloudClientError as e:
            errors.append(f"Apollo publish: {e}")
            steps.append(f"[warn] Apollo publish failed: {e}")

    # Final summary
    result = "\n".join(steps)
    status = "complete" if not errors else "completed with warnings"
    result += f"\n\nDeployment {status}!"
    result += f"\n\nChart: {chart_name}:{new_version}"
    result += f"\nHelm:  {ACR_HELM_PREFIX}/{chart_name}"

    if errors:
        result += "\n\nWarnings:\n" + "\n".join(f"  - {e}" for e in errors)

    return result


def _extract_token(token_text: str) -> str:
    """Extract the raw token from the acr_get_token response text."""
    # The acr_get_token tool returns the token as the text content
    # It may be wrapped in status text, try to find the raw token
    for line in token_text.strip().splitlines():
        line = line.strip()
        # ACR tokens are base64-ish JWT strings, typically long
        if len(line) > 100 and " " not in line:
            return line
    # Fallback: return the whole text stripped
    return token_text.strip()
