"""
Orgo Template - Pre-configure computer environments.

Usage:
    from orgo import Template, Computer

    # Create a template with pre-installed software
    template = Template("python-ml")
    template.run("pip install numpy pandas scikit-learn")
    template.build()

    # Launch computers from the template
    computer = Computer(template="python-ml")
"""

import os
import subprocess
import logging
from typing import Optional, List

from .api.client import ApiClient

logger = logging.getLogger(__name__)


def _print_info(message: str):
    print(f"-> {message}")


def _print_success(message: str):
    print(f"[ok] {message}")


def _print_error(message: str):
    print(f"[error] {message}")


class Template:
    """
    Create reusable computer templates with pre-installed software.

    Templates let you define a base environment once and launch multiple
    computers from it. This is useful for:
    - Pre-installing dependencies (Python packages, apt packages, etc.)
    - Cloning repositories
    - Setting up configuration files

    Examples:
        # Create and build a template
        template = Template("data-science")
        template.run("apt-get update && apt-get install -y python3-pip")
        template.run("pip install numpy pandas matplotlib jupyter")
        template.build()

        # Launch a computer from the template
        computer = Computer(template="data-science")

        # Or pass the template object directly
        computer = Computer(template=template)
    """

    def __init__(self,
                 name: str,
                 workspace: Optional[str] = None,
                 api_key: Optional[str] = None,
                 base_api_url: Optional[str] = None,
                 verbose: bool = True):
        """
        Initialize a Template.

        Args:
            name: Template name (used to reference it later)
            workspace: Workspace to store the template in (auto-created if needed)
            api_key: Orgo API key (defaults to ORGO_API_KEY env var)
            base_api_url: Custom API URL
            verbose: Show console output (default: True)
        """
        self.name = name
        self.verbose = verbose
        self.api_key = api_key or os.environ.get("ORGO_API_KEY")
        self.base_api_url = base_api_url
        self.api = ApiClient(self.api_key, self.base_api_url)

        # Resolve workspace
        workspace_name = workspace or "templates"
        self._resolve_workspace(workspace_name)

        # Build steps (starts with Orgo base image)
        self._steps: List[str] = ["FROM registry.fly.io/orgo-image-repo:latest"]
        self._image_ref: Optional[str] = None

        if self.verbose:
            _print_info(f"Template '{name}' initialized")

    def _resolve_workspace(self, workspace_name: str):
        """Find or create workspace for templates"""
        try:
            workspace = self.api.get_project_by_name(workspace_name)
            self._workspace_id = workspace["id"]
            self._org_id = workspace.get("user_id", "orgo")
        except Exception:
            if self.verbose:
                _print_info(f"Creating workspace '{workspace_name}'...")
            workspace = self.api.create_project(workspace_name)
            self._workspace_id = workspace["id"]
            self._org_id = workspace.get("user_id", "orgo")

    # =========================================================================
    # Build Steps (Chainable)
    # =========================================================================

    def run(self, command: str) -> 'Template':
        """
        Run a command during build (like apt-get, pip install, etc.)

        Args:
            command: Shell command to run

        Returns:
            Self for chaining

        Example:
            template.run("apt-get update")
            template.run("pip install requests")
        """
        self._steps.append(f"RUN {command}")
        return self

    def copy(self, src: str, dest: str) -> 'Template':
        """
        Copy local files into the template.

        Args:
            src: Local file or directory path
            dest: Destination path in the template

        Returns:
            Self for chaining

        Example:
            template.copy("./config.json", "/app/config.json")
            template.copy("./scripts", "/app/scripts")
        """
        self._steps.append(f"COPY {src} {dest}")
        return self

    def env(self, key: str, value: str) -> 'Template':
        """
        Set an environment variable.

        Args:
            key: Environment variable name
            value: Environment variable value

        Returns:
            Self for chaining

        Example:
            template.env("PYTHONPATH", "/app")
            template.env("DEBUG", "true")
        """
        self._steps.append(f"ENV {key}={value}")
        return self

    def workdir(self, path: str) -> 'Template':
        """
        Set the working directory.

        Args:
            path: Directory path

        Returns:
            Self for chaining

        Example:
            template.workdir("/app")
        """
        self._steps.append(f"WORKDIR {path}")
        return self

    def clone(self, repo_url: str, dest: str = "/repo") -> 'Template':
        """
        Clone a git repository.

        Args:
            repo_url: Git repository URL
            dest: Destination directory (default: /repo)

        Returns:
            Self for chaining

        Example:
            template.clone("https://github.com/user/repo.git")
            template.clone("https://github.com/user/repo.git", "/app")
        """
        self._steps.append(f"RUN git clone {repo_url} {dest}")
        return self

    # =========================================================================
    # Build
    # =========================================================================

    def build(self, context: str = ".") -> str:
        """
        Build and publish the template.

        This builds a Docker image with your configuration and pushes it
        to Orgo's registry. After building, you can launch computers from
        this template using Computer(template="name").

        Args:
            context: Build context directory (default: current directory)

        Returns:
            Image reference string

        Example:
            template.run("pip install pandas")
            image_ref = template.build()
            print(f"Built: {image_ref}")
        """
        if len(self._steps) <= 1:
            raise ValueError("No build steps defined. Use .run(), .copy(), etc. first.")

        if self.verbose:
            _print_info(f"Building template '{self.name}'...")

        # Register build with API
        try:
            build_info = self.api.create_build(self._org_id, self._workspace_id, self.name)
            build_data = build_info.get('build', {})

            build_id = build_data.get('id')
            tag = build_data.get('tag')
            image_ref = build_data.get('imageRef')

            if not build_id or not image_ref:
                raise ValueError("Failed to register build")

            if self.verbose:
                _print_info(f"Build ID: {build_id}")
                _print_info(f"Image: {image_ref}")

        except Exception as e:
            _print_error(f"Failed to register build: {e}")
            raise

        # Generate Dockerfile
        dockerfile_path = f"Dockerfile.{tag}"
        logs = []

        try:
            with open(dockerfile_path, "w") as f:
                f.write("\n".join(self._steps))

            # Update status
            self.api.update_build(build_id, "building")

            # Build using local Docker (requires: docker, flyctl auth docker)
            cmd = [
                "docker", "buildx", "build",
                "--platform", "linux/amd64",
                "-t", image_ref,
                "-f", dockerfile_path,
                context,
                "--push"
            ]

            if self.verbose:
                _print_info("Building image...")

            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1
            )

            while True:
                line = process.stdout.readline()
                if not line and process.poll() is not None:
                    break
                if line:
                    if self.verbose:
                        print(line, end="")
                    logs.append(line)

            if process.returncode != 0:
                raise Exception(f"Build failed with exit code {process.returncode}")

            # Success
            self.api.update_build(build_id, "completed", build_log="".join(logs))
            self._image_ref = image_ref

            if self.verbose:
                _print_success(f"Template '{self.name}' built successfully!")

            return image_ref

        except Exception as e:
            _print_error(f"Build failed: {e}")
            try:
                self.api.update_build(build_id, "failed", error_message=str(e), build_log="".join(logs))
            except:
                pass
            raise

        finally:
            if os.path.exists(dockerfile_path):
                os.remove(dockerfile_path)

    # =========================================================================
    # Properties
    # =========================================================================

    @property
    def image_ref(self) -> Optional[str]:
        """Get the built image reference (None if not built yet)."""
        return self._image_ref

    def __repr__(self):
        status = "built" if self._image_ref else "not built"
        return f"Template(name='{self.name}', status='{status}')"
