# mustel/venv.py
"""
🦦 mustel venv - Simple Virtual Environment Manager

Commands:
  mustel venv          Show status (is there a venv? is it active?)
  mustel venv new      Create a venv in current folder
  mustel venv list     Show all venvs on your system
  mustel venv destroy  Delete the venv safely
"""

import sys
import os
import subprocess
import json
import shutil
import platform


# ============================================================
# HELPER FUNCTIONS
# ============================================================

def run_cmd(cmd):
    """Run a command and return output, or None if it fails."""
    try:
        return subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True)
    except Exception:
        return None


def get_folder_size(path):
    """Get folder size in MB."""
    total = 0
    try:
        for dirpath, dirnames, filenames in os.walk(path):
            for f in filenames:
                fp = os.path.join(dirpath, f)
                if os.path.exists(fp):
                    total += os.path.getsize(fp)
    except Exception:
        pass
    return round(total / (1024 * 1024), 1)  # Convert to MB


# ============================================================
# DETECTION FUNCTIONS
# ============================================================

def detect_venv(folder=None):
    """
    Find a venv in the given folder (or current folder).
    
    Returns dict with venv info, or None if no venv found.
    
    We look for these common venv folder names:
      .venv, venv, env, .env
    """
    if folder is None:
        folder = os.getcwd()
    
    # Common venv folder names (in order of preference)
    venv_names = ['.venv', 'venv', 'env', '.env']
    
    for name in venv_names:
        venv_path = os.path.join(folder, name)
        
        # A venv has a pyvenv.cfg file
        cfg_file = os.path.join(venv_path, 'pyvenv.cfg')
        
        if os.path.exists(cfg_file):
            return {
                'name': name,
                'path': venv_path,
                'folder': folder,
                'cfg_file': cfg_file,
            }
    
    return None


def parse_pyvenv_cfg(cfg_file):
    """
    Parse a pyvenv.cfg file and return a dict of key-value pairs.
    
    Example pyvenv.cfg content:
        home = C:/Users/User/AppData/Local/Programs/Python/Python313
        include-system-site-packages = false
        version = 3.13.7
    """
    config = {}
    try:
        with open(cfg_file, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if '=' in line:
                    key, value = line.split('=', 1)
                    config[key.strip()] = value.strip()
    except Exception:
        pass
    return config


def get_venv_info(venv_data):
    """
    Get detailed info about a venv.
    
    Takes the dict from detect_venv() and adds more details:
      - Python version
      - Package count
      - Size in MB
      - Whether it's currently active
      - Whether it's broken (can't run python)
    """
    if not venv_data:
        return None
    
    venv_path = venv_data['path']
    info = venv_data.copy()
    
    # Get Python executable path
    if platform.system() == "Windows":
        python_exe = os.path.join(venv_path, 'Scripts', 'python.exe')
    else:
        python_exe = os.path.join(venv_path, 'bin', 'python')
    
    info['python_exe'] = python_exe
    info['is_broken'] = False
    
    # Try to get Python version by running the executable
    if os.path.exists(python_exe):
        out = run_cmd([python_exe, '--version'])
        if out:
            info['python_version'] = out.strip()
        else:
            # Python exe exists but failed to run - venv is broken
            info['is_broken'] = True
            # Fallback: read version from pyvenv.cfg
            cfg_file = venv_data.get('cfg_file') or os.path.join(venv_path, 'pyvenv.cfg')
            cfg = parse_pyvenv_cfg(cfg_file)
            if 'version' in cfg:
                info['python_version'] = f"Python {cfg['version']} (broken)"
            else:
                info['python_version'] = 'Unknown (broken)'
    else:
        info['python_version'] = 'Not found'
        info['is_broken'] = True
    
    # Get package count (only if venv is not broken)
    if os.path.exists(python_exe) and not info['is_broken']:
        out = run_cmd([python_exe, '-m', 'pip', 'list', '--format=json'])
        if out:
            try:
                packages = json.loads(out)
                info['package_count'] = len(packages)
                info['packages'] = [pkg['name'] for pkg in packages]
            except Exception:
                info['package_count'] = 0
                info['packages'] = []
        else:
            info['package_count'] = 0
            info['packages'] = []
    else:
        info['package_count'] = '?'
        info['packages'] = []
    
    # Get size
    info['size_mb'] = get_folder_size(venv_path)
    
    # Check if active (compare sys.prefix with venv path)
    current_prefix = os.path.normcase(os.path.realpath(sys.prefix))
    venv_real = os.path.normcase(os.path.realpath(venv_path))
    info['is_active'] = current_prefix == venv_real
    
    return info


def is_any_venv_active():
    """Check if ANY venv is currently active."""
    # When a venv is active, sys.prefix differs from sys.base_prefix
    return sys.prefix != sys.base_prefix


def get_active_venv_path():
    """Get the path of the currently active venv, or None."""
    if is_any_venv_active():
        return sys.prefix
    return None


# ============================================================
# ACTION FUNCTIONS
# ============================================================

def create_venv(folder=None, python_exe=None):
    """
    Create a new venv in the given folder.
    
    Args:
        folder: Where to create (default: current folder)
        python_exe: Which Python to use (default: current Python)
    
    Returns:
        (success: bool, message: str)
    """
    if folder is None:
        folder = os.getcwd()
    
    if python_exe is None:
        python_exe = sys.executable
    
    venv_path = os.path.join(folder, '.venv')
    
    # Check if venv already exists
    if os.path.exists(venv_path):
        return False, f"Venv already exists at {venv_path}"
    
    # Create the venv
    try:
        result = subprocess.run(
            [python_exe, '-m', 'venv', venv_path],
            capture_output=True,
            text=True
        )
        
        if result.returncode == 0:
            return True, venv_path
        else:
            return False, result.stderr or "Unknown error"
    except Exception as e:
        return False, str(e)


def destroy_venv(venv_path):
    """
    Delete a venv folder.
    
    Returns:
        (success: bool, message: str)
    """
    import stat
    import time
    
    def handle_remove_readonly(func, path, exc_info):
        """Error handler for shutil.rmtree on Windows - removes read-only flag and retries."""
        # If permission denied, try to remove read-only flag
        try:
            os.chmod(path, stat.S_IWRITE)
            func(path)
        except Exception:
            pass
    
    try:
        if not os.path.exists(venv_path):
            return False, "Venv not found"
        
        # Try standard removal with error handler for read-only files
        try:
            shutil.rmtree(venv_path, onerror=handle_remove_readonly)
            return True, "Venv deleted"
        except Exception as e1:
            # If that fails on Windows, try using rmdir /s /q command
            if platform.system() == "Windows":
                try:
                    # Give OneDrive a moment to release locks
                    time.sleep(0.5)
                    result = subprocess.run(
                        ['cmd', '/c', 'rmdir', '/s', '/q', venv_path],
                        capture_output=True,
                        text=True
                    )
                    if result.returncode == 0 or not os.path.exists(venv_path):
                        return True, "Venv deleted"
                    else:
                        return False, f"Failed: {result.stderr or str(e1)}"
                except Exception as e2:
                    return False, str(e2)
            else:
                return False, str(e1)
    except Exception as e:
        return False, str(e)


def find_all_venvs(search_dirs=None):
    """
    Find all venvs in common project locations.
    
    Searches these folders:
      - User's home directory project folders
      - Common project locations
      - Desktop, Documents
    
    Returns list of venv info dicts.
    """
    if search_dirs is None:
        home = os.path.expanduser('~')
        search_dirs = [
            home,
            os.path.join(home, 'Desktop'),
            os.path.join(home, 'Documents'),
            os.path.join(home, 'Projects'),
            os.path.join(home, 'Code'),
            os.path.join(home, 'Dev'),
            os.path.join(home, 'OneDrive', 'Desktop'),
            os.path.join(home, 'OneDrive', 'Documents'),
        ]
    
    venvs = []
    checked = set()
    
    for search_dir in search_dirs:
        if not os.path.exists(search_dir):
            continue
        
        # Look 2 levels deep to find projects with venvs
        try:
            for item in os.listdir(search_dir):
                item_path = os.path.join(search_dir, item)
                
                if not os.path.isdir(item_path):
                    continue
                
                # Skip hidden folders (except common venv names)
                if item.startswith('.') and item not in ['.venv', '.env']:
                    continue
                
                # Check if this folder is a project with a venv
                venv_data = detect_venv(item_path)
                if venv_data:
                    real_path = os.path.normcase(os.path.realpath(venv_data['path']))
                    if real_path not in checked:
                        checked.add(real_path)
                        info = get_venv_info(venv_data)
                        if info:
                            info['project_name'] = item
                            venvs.append(info)
                else:
                    # Check one level deeper (for nested project folders like ~/Desktop/mustel2/mustel)
                    try:
                        for subitem in os.listdir(item_path):
                            subitem_path = os.path.join(item_path, subitem)
                            if not os.path.isdir(subitem_path):
                                continue
                            if subitem.startswith('.') and subitem not in ['.venv', '.env']:
                                continue
                            
                            venv_data = detect_venv(subitem_path)
                            if venv_data:
                                real_path = os.path.normcase(os.path.realpath(venv_data['path']))
                                if real_path not in checked:
                                    checked.add(real_path)
                                    info = get_venv_info(venv_data)
                                    if info:
                                        info['project_name'] = f"{item}/{subitem}"
                                        venvs.append(info)
                    except PermissionError:
                        continue
        except PermissionError:
            continue
    
    return venvs


# ============================================================
# DISPLAY FUNCTIONS (The Visual UI!)
# ============================================================

def draw_box(title, lines, width=60):
    """Draw a nice box around content."""
    print(f"╭{'─' * (width - 2)}╮")
    print(f"│  {title:<{width - 5}}│")
    print(f"├{'─' * (width - 2)}┤")
    for line in lines:
        # Truncate if too long
        if len(line) > width - 4:
            line = line[:width - 7] + "..."
        print(f"│  {line:<{width - 5}}│")
    print(f"╰{'─' * (width - 2)}╯")


# ============================================================
# COMMAND FUNCTIONS (cmd_* pattern like main.py)
# ============================================================

def cmd_venv_status():
    """Show venv status dashboard - the main 'mustel venv' command."""
    cwd = os.getcwd()
    venv_data = detect_venv(cwd)
    
    print()
    
    if venv_data:
        # Venv exists - show full info
        info = get_venv_info(venv_data)
        
        if info.get('is_broken'):
            status = "⚠️  BROKEN"
        elif info['is_active']:
            status = "✅ ACTIVE"
        else:
            status = "⚪ Inactive"
        
        lines = [
            "",
            f"📍 Folder: {cwd}",
            "",
            f"Status: {status}",
            f"Path: {info['name']}/",
            f"Python: {info['python_version']}",
            f"Packages: {info['package_count']}",
            f"Size: {info['size_mb']} MB",
            "",
        ]
        
        if info.get('is_broken'):
            lines.append("⚠️  This venv is broken!")
            lines.append("   It was created on a different machine.")
            lines.append("")
            lines.append("💡 To fix, delete and recreate it:")
            lines.append("   mustel venv destroy")
            lines.append("   mustel venv new")
            lines.append("")
        elif not info['is_active']:
            lines.append("💡 To activate, run:")
            if platform.system() == "Windows":
                lines.append(f"   {info['name']}\\Scripts\\activate")
            else:
                lines.append(f"   source {info['name']}/bin/activate")
            lines.append("")
        
        draw_box("🦦 mustel venv", lines)
    
    else:
        # No venv - show helpful message
        active_path = get_active_venv_path()
        
        lines = [
            "",
            f"📍 Folder: {cwd}",
            "",
            "⚠️  No venv detected in this folder",
            "",
        ]
        
        if active_path:
            lines.append(f"📢 But you have an active venv from:")
            lines.append(f"   {active_path}")
            lines.append("")
        
        lines.append("💡 Create one with:")
        lines.append("   mustel venv new")
        lines.append("")
        
        draw_box("🦦 mustel venv", lines)
    
    # Always show quick commands
    print()
    print("  Commands:")
    print("    mustel venv new      Create venv here")
    print("    mustel venv list     Show all venvs")
    print("    mustel venv destroy  Remove venv")
    print()


def cmd_venv_new():
    """Create a new venv."""
    cwd = os.getcwd()
    
    print()
    print("🦦 mustel venv new")
    print()
    
    # Check if venv already exists
    existing = detect_venv(cwd)
    if existing:
        print(f"⚠️  A venv already exists: {existing['name']}/")
        print()
        print("   To remove it first: mustel venv destroy")
        return
    
    print(f"📍 Creating .venv in: {cwd}")
    print(f"🐍 Using: {sys.executable}")
    print()
    print("⏳ Please wait...")
    print()
    
    success, result = create_venv(cwd)
    
    if success:
        print("✅ Venv created successfully!")
    else:
        print(f"❌ Failed to create venv: {result}")

def cmd_venv_list():
    """List all venvs on the system."""
    print()
    print("🦦 mustel venv list")
    print()
    print("⏳ Scanning for venvs...")
    print()
    
    venvs = find_all_venvs()
    
    if not venvs:
        print("No venvs found in common project locations.")
        print()
        print("We searched: ~/Desktop, ~/Documents, ~/Projects, etc.")
        return
    
    # Calculate totals (skip non-numeric package counts from broken venvs)
    total_packages = sum(v.get('package_count', 0) for v in venvs if isinstance(v.get('package_count'), int))
    total_size = sum(v.get('size_mb', 0) for v in venvs)
    
    # Header
    print(f"  {'PROJECT':<25} {'STATUS':<12} {'PKGS':<8} {'SIZE':<10}")
    print("  " + "─" * 55)
    
    for v in venvs:
        project = v.get('project_name', '?')[:24]
        if v.get('is_broken'):
            status = "⚠ broken"
        elif v.get('is_active'):
            status = "● active"
        else:
            status = "○ inactive"
        pkgs = str(v.get('package_count', 0))
        size = f"{v.get('size_mb', 0)} MB"
        
        print(f"  {project:<25} {status:<12} {pkgs:<8} {size:<10}")
    
    print("  " + "─" * 55)
    print(f"  Total: {len(venvs)} venvs │ {total_packages} packages │ {total_size} MB")
    print()


def cmd_venv_destroy():
    """Delete the venv (with confirmation)."""
    cwd = os.getcwd()
    venv_data = detect_venv(cwd)
    
    print()
    print("🦦 mustel venv destroy")
    print()
    
    if not venv_data:
        print("⚠️  No venv found in this folder")
        return
    
    info = get_venv_info(venv_data)
    
    if info['is_active']:
        print("⚠️  This venv is currently ACTIVE!")
        print()
        print("   First deactivate it by running: deactivate")
        print("   Then run: mustel venv destroy")
        return
    
    # Show what will be deleted
    print(f"📍 Venv: {info['path']}")
    print(f"   Python: {info['python_version']}")
    print(f"   Packages: {info['package_count']}")
    print(f"   Size: {info['size_mb']} MB")
    print()
    
    # Ask for confirmation
    try:
        answer = input("❓ Delete this venv? (y/N): ").strip().lower()
    except KeyboardInterrupt:
        print("\n\n   Cancelled.")
        return
    
    if answer != 'y':
        print()
        print("   Cancelled.")
        return
    
    print()
    print("⏳ Deleting...")
    
    success, message = destroy_venv(info['path'])
    
    if success:
        print("✅ Venv deleted successfully!")
    else:
        print(f"❌ Failed: {message}")


def cmd_venv_on():
    """Show how to activate the venv."""
    cwd = os.getcwd()
    venv_data = detect_venv(cwd)
    
    print()
    print("🦦 mustel venv on")
    print()
    
    if not venv_data:
        print("⚠️  No venv found in this folder")
        print()
        print("   Create one first:")
        print("   mustel venv new")
        return
    
    info = get_venv_info(venv_data)
    
    if info['is_active']:
        print("✅ This venv is already ACTIVE!")
        return

    print(f"📍 Venv found: {info['name']}/")
    print()
    print("📢 To activate, run:")
    print()
    
    if platform.system() == "Windows":
        print(f"    {info['name']}\\Scripts\\activate")
    else:
        print(f"    source {info['name']}/bin/activate")
    
    print()


def cmd_venv_off():
    """Show how to deactivate."""
    print()
    print("🦦 mustel venv off")
    print()
    
    if is_any_venv_active():
        print("📢 To deactivate, run:")
        print()
        print("    deactivate")
    else:
        print("ℹ️  No venv is currently active.")
    
    print()


def cmd_venv_help():
    """Show venv help."""
    print(r"""
🦦 mustel venv - Virtual Environment Manager

Commands:
  mustel venv          Show status dashboard
  mustel venv new      Create a new .venv
  mustel venv on       Show activation command
  mustel venv off      Show deactivation command
  mustel venv list     Find all venvs on your system
  mustel venv destroy  Delete the venv (with confirmation)

What is a venv?
  A virtual environment is a separate Python installation
  just for one project. It keeps your projects isolated
  so packages from one project don't interfere with another.

Quick start:
  1. cd to your project folder
  2. mustel venv new
  3. Activate: .venv\Scripts\activate  (Windows)
               source .venv/bin/activate  (Mac/Linux)
  4. pip install whatever you need
  5. When done: deactivate
""")


# ============================================================
# MAIN HANDLER
# ============================================================

def handle_venv(args):
    """
    Main entry point for all venv commands.
    
    Called from main.py like: handle_venv(args[1:])
    """
    if not args:
        cmd_venv_status()
    elif args[0] in ('help', '-h', '--help'):
        cmd_venv_help()
    elif args[0] == 'new':
        cmd_venv_new()
    elif args[0] == 'on':
        cmd_venv_on()
    elif args[0] == 'off':
        cmd_venv_off()
    elif args[0] == 'list':
        cmd_venv_list()
    elif args[0] in ('destroy', 'delete', 'remove', 'rm'):
        cmd_venv_destroy()
    else:
        print(f"Unknown venv command: {args[0]}")
        cmd_venv_help()
