"""Helpers to install a Python package from a git repository.

Supports three modes:
- pip: pip install directly from git+https URL
- editable: shallow git clone then pip install -e (editable)
- wheel: clone, build wheel, install wheel

Note: For private repositories, the caller must provide appropriate git credentials (SSH key or tokenized URL).
"""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
import tempfile
from typing import Optional


def _git_url(repo: str) -> str:
    # Accept both 'owner/repo' and full URL
    if repo.startswith("http:") or repo.startswith("https:") or repo.startswith("git@"):
        return repo
    return f"https://github.com/{repo}.git"

def install_from_git(
    repo: str,
    branch: str = None,
    mode: str = "pip",
    workspace: str = None,
    use_uv: bool = False,
    install_cmd: str = None,
    fallback_on_error: bool = False,
) -> dict:
    """
    Install a Python package from a git repository.

    Args:
        repo: Repository URL or GitHub shorthand (e.g., 'owner/repo')
        branch: Branch to install from (optional)
        mode: Installation mode - 'pip', 'editable', or 'wheel'
        workspace: Directory to clone into (optional)
        use_uv: Use uv instead of pip for installation
        install_cmd: Custom install command to run after cloning (overrides mode)
        fallback_on_error: If True, try standard installation if custom command fails

    Returns:
        Dictionary with installation details
    """
    git_url = _git_url(repo)

    def _ensure_pip(python_exec: str) -> bool:
        """Ensure that the given python executable has pip available.

        Returns True if pip is available or was successfully bootstrapped, False otherwise.
        """
        if use_uv:
            # When using uv, we don't need to ensure pip
            return True
            
        try:
            subprocess.run([python_exec, "-m", "pip", "--version"], check=True, stdout=subprocess.DEVNULL)
            return True
        except subprocess.CalledProcessError:
            # try to bootstrap pip using ensurepip
            try:
                subprocess.run([python_exec, "-m", "ensurepip", "--upgrade"], check=True)
                subprocess.run([python_exec, "-m", "pip", "install", "--upgrade", "pip"], check=True)
                return True
            except subprocess.CalledProcessError:
                return False
        except FileNotFoundError:
            return False

    def _check_uv() -> bool:
        """Check if uv is available."""
        try:
            subprocess.run(["uv", "--version"], check=True, stdout=subprocess.DEVNULL)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            return False

    # Validate uv availability if requested
    if use_uv and not _check_uv():
        return {"status": "error", "path": None, "message": "uv not found - install uv first"}

    try:
        # Handle custom install command first
        if install_cmd:
            # Custom command requires cloning first
            target_base = workspace or tempfile.mkdtemp(prefix="quicktester_install_")
            os.makedirs(target_base, exist_ok=True)
            dest = os.path.join(target_base, "repo_clone")

            # remove dest if exists (fresh clone)
            if os.path.exists(dest):
                shutil.rmtree(dest)

            clone_cmd = ["git", "clone", "--depth", "1"]
            if branch:
                clone_cmd += ["--branch", branch]
            clone_cmd += [git_url, dest]
            subprocess.run(clone_cmd, check=True)

            # Parse and run custom command(s)
            import shlex
            
            # Support multiple commands separated by semicolon
            commands = [cmd.strip() for cmd in install_cmd.split(';') if cmd.strip()]
            
            for i, cmd in enumerate(commands):
                cmd_parts = shlex.split(cmd)
                try:
                    print(f"Running custom command ({i+1}/{len(commands)}): {cmd}")
                    subprocess.run(cmd_parts, check=True, cwd=dest)
                    return {"status": "ok", "path": dest, "message": f"installed with custom command: {cmd}"}
                except subprocess.CalledProcessError as e:
                    error_msg = f"Custom command '{cmd}' failed with exit code {e.returncode}"
                    print(f"Warning: {error_msg}")
                    
                    # If this is not the last command, try the next one
                    if i < len(commands) - 1:
                        print(f"Trying next command...")
                        continue
                    
                    # This was the last command and it failed
                    if fallback_on_error:
                        print(f"All custom commands failed. Falling back to standard installation mode: {mode}")
                        # Continue to standard installation logic below
                        break
                    else:
                        return {
                            "status": "error", 
                            "path": dest, 
                            "message": f"All custom commands failed. Last error: {error_msg}",
                            "suggestion": "Try running the commands manually in the cloned directory to debug, or use --install-fallback flag"
                        }

        # Mode: install directly from git
        if mode == "pip":
            if use_uv:
                spec = f"git+{git_url}"
                if branch:
                    spec = f"git+{git_url}@{branch}"
                cmd = ["uv", "pip", "install", spec]
                subprocess.run(cmd, check=True)
            else:
                python_exec = sys.executable
                _ensure_pip(python_exec)
                spec = f"git+{git_url}"
                if branch:
                    spec = f"git+{git_url}@{branch}"
                cmd = [python_exec, "-m", "pip", "install", spec]
                subprocess.run(cmd, check=True)
            return {"status": "ok", "path": None, "message": "installed via pip"}

        # For editable/wheel we need to clone the repo first
        target_base = workspace or tempfile.mkdtemp(prefix="quicktester_install_")
        os.makedirs(target_base, exist_ok=True)
        dest = os.path.join(target_base, "repo_clone")

        # remove dest if exists (fresh clone)
        if os.path.exists(dest):
            shutil.rmtree(dest)

        clone_cmd = ["git", "clone", "--depth", "1"]
        if branch:
            clone_cmd += ["--branch", branch]
        clone_cmd += [git_url, dest]
        subprocess.run(clone_cmd, check=True)

        # editable
        if mode == "editable":
            if use_uv:
                cmd = ["uv", "pip", "install", "-e", dest]
                subprocess.run(cmd, check=True)
            else:
                python_exec = sys.executable
                _ensure_pip(python_exec)
                cmd = [python_exec, "-m", "pip", "install", "-e", dest]
                subprocess.run(cmd, check=True)
            return {"status": "ok", "path": dest, "message": "installed editable"}

        # wheel
        if mode == "wheel":
            if use_uv:
                # Build and install wheel with uv
                subprocess.run(["uv", "pip", "install", "build"], check=True)
                subprocess.run(["uv", "run", "python", "-m", "build", "-w"], check=True, cwd=dest)
                dist_dir = os.path.join(dest, "dist")
                wheels = [f for f in os.listdir(dist_dir) if f.endswith(".whl")] if os.path.isdir(dist_dir) else []
                if not wheels:
                    return {"status": "error", "path": dest, "message": "no wheel produced"}
                wheel_path = os.path.join(dist_dir, wheels[0])
                subprocess.run(["uv", "pip", "install", wheel_path], check=True)
            else:
                python_exec = sys.executable
                _ensure_pip(python_exec)
                subprocess.run([python_exec, "-m", "pip", "install", "build"], check=True)
                subprocess.run([python_exec, "-m", "build", "-w", dest], check=True, cwd=dest)
                dist_dir = os.path.join(dest, "dist")
                wheels = [f for f in os.listdir(dist_dir) if f.endswith(".whl")] if os.path.isdir(dist_dir) else []
                if not wheels:
                    return {"status": "error", "path": dest, "message": "no wheel produced"}
                wheel_path = os.path.join(dist_dir, wheels[0])
                subprocess.run([python_exec, "-m", "pip", "install", wheel_path], check=True)
            return {"status": "ok", "path": dest, "message": "installed wheel"}

        return {"status": "error", "path": None, "message": f"unknown mode: {mode}"}

    except subprocess.CalledProcessError as e:
        return {"status": "error", "path": None, "message": str(e)}
