# Copyright (c) Metis. All rights reserved.

"""Agent image build strategies for Mantis platform.

Packaging strategy (convention-over-configuration):
    mantis deploy
      ├── Dockerfile exists?
      │   ├── YES → docker build
      │   └── NO  → nixpacks installed?
      │            ├── YES → nixpacks build (auto-detect, optimized)
      │            └── NO  → generate simple Python template (fallback)
      └── validate mantis_manifest/ → build → push → register
"""

from __future__ import annotations

import logging
import shutil
import subprocess
import tempfile
from enum import Enum
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)


class BuildStrategy(str, Enum):
    DOCKERFILE = "dockerfile"
    NIXPACKS = "nixpacks"
    TEMPLATE = "template"


FALLBACK_DOCKERFILE = """\
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt* ./
RUN pip install --no-cache-dir -r requirements.txt 2>/dev/null || true
COPY . .
CMD ["python", "-m", "agent"]
"""


def detect_build_strategy(
    project_dir: Path,
    dockerfile: Optional[str] = None,
) -> BuildStrategy:
    """Detect the best build strategy for the project.

    Args:
        project_dir: Path to the agent project directory.
        dockerfile: Explicit Dockerfile path override.

    Returns:
        The detected BuildStrategy.
    """
    # Explicit Dockerfile specified
    if dockerfile:
        df_path = project_dir / dockerfile
        if df_path.exists():
            return BuildStrategy.DOCKERFILE
        raise FileNotFoundError(f"Specified Dockerfile not found: {df_path}")

    # Auto-detect Dockerfile
    if (project_dir / "Dockerfile").exists():
        logger.info("Found Dockerfile in project directory")
        return BuildStrategy.DOCKERFILE

    # Check for nixpacks
    if shutil.which("nixpacks"):
        logger.info("No Dockerfile found, nixpacks is available")
        return BuildStrategy.NIXPACKS

    # Fallback to template
    logger.info("No Dockerfile or nixpacks found, using fallback template")
    return BuildStrategy.TEMPLATE


def build_image(
    project_dir: Path,
    tag: str,
    *,
    dockerfile: Optional[str] = None,
    strategy: Optional[BuildStrategy] = None,
) -> str:
    """Build a Docker image using the detected or specified strategy.

    Args:
        project_dir: Path to the agent project directory.
        tag: Docker image tag (e.g., "myagent:v1").
        dockerfile: Explicit Dockerfile path (relative to project_dir).
        strategy: Force a specific build strategy.

    Returns:
        The image tag that was built.

    Raises:
        RuntimeError: If the build fails.
        FileNotFoundError: If specified Dockerfile doesn't exist.
    """
    if strategy is None:
        strategy = detect_build_strategy(project_dir, dockerfile)

    logger.info(f"Building image '{tag}' using strategy: {strategy.value}")

    if strategy == BuildStrategy.DOCKERFILE:
        return _build_with_dockerfile(project_dir, tag, dockerfile)
    elif strategy == BuildStrategy.NIXPACKS:
        return _build_with_nixpacks(project_dir, tag)
    elif strategy == BuildStrategy.TEMPLATE:
        return _build_with_template(project_dir, tag)
    else:
        raise ValueError(f"Unknown build strategy: {strategy}")


def get_image_digest(tag: str) -> Optional[str]:
    """Extract the content-addressable digest of a built Docker image.

    Args:
        tag: Docker image tag to inspect.

    Returns:
        Image digest (e.g., ``sha256:abc123...``) or None if extraction fails.
    """
    try:
        result = subprocess.run(
            ["docker", "inspect", "--format", "{{.Id}}", tag],
            capture_output=True,
            text=True,
        )
        if result.returncode == 0:
            digest = result.stdout.strip()
            logger.debug(f"Image digest for '{tag}': {digest}")
            return digest
    except Exception as e:
        logger.warning(f"Could not extract image digest for '{tag}': {e}")
    return None


def push_image(tag: str) -> None:
    """Push a Docker image to its registry.

    Args:
        tag: Image tag to push.

    Raises:
        RuntimeError: If the push fails.
    """
    logger.info(f"Pushing image '{tag}'...")
    result = subprocess.run(
        ["docker", "push", tag],
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        raise RuntimeError(f"docker push failed: {result.stderr}")
    logger.info(f"Pushed image '{tag}'")


def _build_with_dockerfile(
    project_dir: Path,
    tag: str,
    dockerfile: Optional[str] = None,
) -> str:
    """Build using an existing Dockerfile."""
    cmd = ["docker", "build", "-t", tag]
    if dockerfile:
        cmd.extend(["-f", str(project_dir / dockerfile)])
    cmd.append(str(project_dir))

    logger.info(f"Running: {' '.join(cmd)}")
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"docker build failed:\n{result.stderr}")

    logger.info(f"Built image '{tag}' from Dockerfile")
    return tag


def _build_with_nixpacks(project_dir: Path, tag: str) -> str:
    """Build using nixpacks (auto-detect runtime)."""
    cmd = ["nixpacks", "build", str(project_dir), "--name", tag]

    logger.info(f"Running: {' '.join(cmd)}")
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"nixpacks build failed:\n{result.stderr}")

    logger.info(f"Built image '{tag}' via nixpacks")
    return tag


def _build_with_template(project_dir: Path, tag: str) -> str:
    """Build using a generated fallback Dockerfile."""
    # Write temporary Dockerfile
    tmp_dockerfile = project_dir / ".mantis-Dockerfile.tmp"
    try:
        tmp_dockerfile.write_text(FALLBACK_DOCKERFILE)
        logger.info("Generated fallback Dockerfile (Python 3.12 template)")

        cmd = [
            "docker",
            "build",
            "-t",
            tag,
            "-f",
            str(tmp_dockerfile),
            str(project_dir),
        ]

        logger.info(f"Running: {' '.join(cmd)}")
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            raise RuntimeError(f"docker build (template) failed:\n{result.stderr}")

        logger.info(f"Built image '{tag}' from fallback template")
        return tag
    finally:
        # Clean up temp Dockerfile
        if tmp_dockerfile.exists():
            tmp_dockerfile.unlink()
