# mustel/main.py
"""
mustel - Simple Python environment inspector
A tool to see what's installed across all your Python versions.
"""
import sys
import os
import subprocess
import json
import platform
import importlib.util

__version__ = "0.1.2"

# Fix Windows console encoding for emojis
if platform.system() == "Windows":
    try:
        sys.stdout.reconfigure(encoding='utf-8')
    except Exception:
        pass


# ============================================================
# 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_packages(python_path):
    """Get dict of {package_name: version} for a Python installation."""
    out = run_cmd([python_path, "-m", "pip", "list", "--format=json"])
    if not out:
        return {}
    try:
        data = json.loads(out)
        return {pkg["name"].lower(): pkg["version"] for pkg in data}
    except Exception:
        return {}


def get_outdated(python_path):
    """Get dict of {package_name: (current, latest)} for outdated packages."""
    out = run_cmd([python_path, "-m", "pip", "list", "--outdated", "--format=json"])
    if not out:
        return {}
    try:
        data = json.loads(out)
        return {pkg["name"].lower(): (pkg["version"], pkg["latest_version"]) for pkg in data}
    except Exception:
        return {}


def find_all_pythons():
    """Find all Python installations on the system."""
    found = set()
    current = sys.executable
    
    if platform.system() == "Windows":
        # Check common Windows install location
        local_programs = os.path.expandvars(r"%LOCALAPPDATA%\Programs\Python")
        if os.path.exists(local_programs):
            for folder in os.listdir(local_programs):
                exe = os.path.join(local_programs, folder, "python.exe")
                if os.path.exists(exe):
                    found.add(exe)
        
        # Check where python finds
        out = run_cmd(["where", "python"])
        if out:
            for line in out.splitlines():
                line = line.strip()
                if line.lower().endswith(".exe"):
                    found.add(line)
    else:
        # Unix: check python3.x versions
        for minor in range(7, 15):
            out = run_cmd(["which", f"python3.{minor}"])
            if out:
                found.add(out.strip())
    
    # Remove current python and normalize paths
    result = []
    current_real = os.path.normcase(os.path.realpath(current))
    seen = set()
    
    for path in found:
        try:
            path_real = os.path.normcase(os.path.realpath(path))
            if path_real == current_real:
                continue
            if path_real not in seen:
                seen.add(path_real)
                result.append(path)
        except Exception:
            pass
    
    return result


def is_importable(name):
    """Check if a module can be imported."""
    return importlib.util.find_spec(name) is not None


# ============================================================
# COMMANDS
# ============================================================

def cmd_list():
    """Show packages in current Python."""
    exe = sys.executable
    version = sys.version.split()[0]
    
    print(f"\n🦦 mustel — Python {version}\n")
    print(f"Path: {exe}\n")
    
    packages = get_packages(exe)
    if packages:
        print(f"📦 {len(packages)} packages installed:\n")
        for name in sorted(packages):
            print(f"  {name} == {packages[name]}")
    else:
        print("  (no packages found)")


def cmd_all():
    """Show all Python installations."""
    print("\n🦦 mustel — all pythons\n")
    
    current = sys.executable
    print("⏳ Scanning for Python installations... (this may take a moment)\n")
    others = find_all_pythons()
    all_pythons = [current] + others
    
    print(f"Found {len(all_pythons)} Python(s):\n")
    print("-" * 60)
    
    for exe in all_pythons:
        # Get version
        out = run_cmd([exe, "--version"])
        version = out.strip() if out else "unknown"
        
        # Get package count
        packages = get_packages(exe)
        count = len(packages)
        
        # Mark current
        marker = "★" if exe == current else " "
        label = "(current)" if exe == current else ""
        
        print(f"{marker} {version} {label}")
        print(f"  Path: {exe}")
        print(f"  Packages: {count}\n")
    
    print("-" * 60)


def cmd_diff():
    """Show packages in other Pythons that aren't in current."""
    print("\n🦦 mustel — diff\n")
    
    current = sys.executable
    print("⏳ Analyzing packages and finding other Pythons... please wait...\n")
    current_packages = get_packages(current)
    others = find_all_pythons()
    
    if not others:
        print("No other Python installations found.")
        return
    
    print(f"Current Python: {current}")
    print(f"Packages: {len(current_packages)}\n")
    
    for other in others:
        other_packages = get_packages(other)
        if not other_packages:
            continue
        
        # Find packages in other that aren't in current
        missing = {k: v for k, v in other_packages.items() if k not in current_packages}
        
        out = run_cmd([other, "--version"])
        version = out.strip() if out else other
        
        print(f"\n{version}")
        print(f"  Path: {other}")
        
        if missing:
            print(f"  📦 {len(missing)} unique packages:")
            for name in sorted(missing):
                print(f"    {name} == {missing[name]}")
        else:
            print("  ✅ No unique packages")


def cmd_check(package_name):
    """Check if a package exists and where."""
    print(f"\n🦦 mustel — check '{package_name}'\n")
    
    current = sys.executable
    
    # Check if importable in current
    if is_importable(package_name):
        print(f"✅ '{package_name}' is available in current Python")
        return
    
    print(f"❌ '{package_name}' is NOT in current Python ({current})\n")
    
    # Search other Pythons
    print("⏳ Searching other Python versions...\n")
    others = find_all_pythons()
    found_in = []
    
    for other in others:
        packages = get_packages(other)
        if package_name.lower() in packages:
            found_in.append((other, packages[package_name.lower()]))
    
    if found_in:
        print("Found in:")
        for path, version in found_in:
            print(f"  {path} -> {version}")
        print(f"\nTo install: pip install {package_name}")
    else:
        print("Not found in any Python installation.")


def cmd_updates():
    """Show outdated packages."""
    print("\n🦦 mustel — updates\n")
    
    current = sys.executable
    print(f"Python: {current}\n")
    print("⏳ Checking... (this takes 1-2 minutes)\n")
    
    outdated = get_outdated(current)
    
    if not outdated:
        print("✅ All packages are up to date!")
        return
    
    print(f"📦 {len(outdated)} updates available:\n")
    for name in sorted(outdated):
        old, new = outdated[name]
        print(f"  {name}: {old} → {new}")


def cmd_install(package_name):
    """Install a package using pip."""
    print(f"\n🦦 mustel — install '{package_name}'\n")
    
    current = sys.executable
    print(f"Installing to: {current}\n")
    
    # Check if already installed
    packages = get_packages(current)
    if package_name.lower() in packages:
        version = packages[package_name.lower()]
        print(f"⚠️  '{package_name}' is already installed (version {version})")
        print(f"\nTo upgrade, run: pip install --upgrade {package_name}")
        return
    
    print(f"⏳ Installing {package_name}...\n")
    
    # Run pip install (using subprocess.run so output shows in real-time)
    result = subprocess.run(
        [current, "-m", "pip", "install", package_name],
        text=True
    )
    
    # Check if it worked
    if result.returncode == 0:
        print(f"\n✅ Successfully installed {package_name}!")
    else:
        print(f"\n❌ Failed to install {package_name}")
        print("   Check the error message above.")


def is_major_upgrade(old_version, new_version):
    """Check if this is a major version upgrade (e.g., 2.x -> 3.x)."""
    try:
        old_major = int(old_version.split('.')[0])
        new_major = int(new_version.split('.')[0])
        return new_major > old_major
    except Exception:
        return False


def cmd_upgrade(safe_mode=False):
    """Upgrade all outdated packages."""
    print("\n🦦 mustel upgrade")
    if safe_mode:
        print("   (safe mode: skipping major version upgrades)")
    print()
    
    current = sys.executable
    print("⏳ Checking for outdated packages...\n")
    
    outdated = get_outdated(current)
    
    if not outdated:
        print("✅ All packages are up to date!")
        return
    
    # In safe mode, filter out major version upgrades
    if safe_mode:
        skipped = {}
        safe_updates = {}
        for name, (old, new) in outdated.items():
            if is_major_upgrade(old, new):
                skipped[name] = (old, new)
            else:
                safe_updates[name] = (old, new)
        
        if skipped:
            print(f"⚠️  Skipping {len(skipped)} major version upgrades:")
            for name, (old, new) in skipped.items():
                print(f"     {name}: {old} → {new}")
            print()
        
        outdated = safe_updates
        
        if not outdated:
            print("✅ No safe updates available.")
            print("   Run 'mustel upgrade' without --safe for major upgrades.")
            return
    
    print(f"Found {len(outdated)} packages to upgrade:")
    for name, (old, new) in outdated.items():
        print(f"  {name:20} {old} → {new}")
    
    print()
    try:
        answer = input("❓ Upgrade all? (y/N): ").strip().lower()
    except KeyboardInterrupt:
        print("\n\n   Cancelled.")
        return
    
    if answer != 'y':
        print("\n   Cancelled.")
        return
    
    print("\n⏳ Upgrading...\n")
    
    failed = []
    success = 0
    for name in outdated:
        result = subprocess.run(
            [current, "-m", "pip", "install", "--upgrade", name],
            capture_output=True,
            text=True
        )
        if result.returncode == 0:
            print(f"  ✅ {name}")
            success += 1
        else:
            print(f"  ❌ {name}")
            failed.append(name)
    
    print()
    if failed:
        print(f"⚠️  {len(failed)} packages failed to upgrade:")
        for name in failed:
            print(f"     {name}")
    else:
        print(f"✅ Done! {success} packages upgraded.")


def cmd_clean(target=None):
    """Clean Python cache files."""
    print("\n🦦 mustel clean\n")
    
    if target is None:
        # Show what can be cleaned
        print("🔍 Scanning for cleanable files...\n")
        
        # Check pip cache
        pip_cache_out = run_cmd([sys.executable, "-m", "pip", "cache", "info"])
        pip_cache_size = "Unknown"
        if pip_cache_out:
            for line in pip_cache_out.split('\n'):
                if 'Size' in line or 'size' in line:
                    pip_cache_size = line.split(':')[-1].strip()
                    break
        
        # Count __pycache__ in current folder
        pycache_count = 0
        pycache_size = 0
        cwd = os.getcwd()
        for root, dirs, files in os.walk(cwd):
            if '__pycache__' in dirs:
                pycache_count += 1
                cache_path = os.path.join(root, '__pycache__')
                for f in os.listdir(cache_path):
                    fp = os.path.join(cache_path, f)
                    if os.path.isfile(fp):
                        pycache_size += os.path.getsize(fp)
        
        pycache_mb = round(pycache_size / (1024 * 1024), 2)
        
        print(f"  📁 pip cache        {pip_cache_size}")
        print(f"  📁 __pycache__      {pycache_count} folders ({pycache_mb} MB)")
        print()
        print("🧹 Clean options:")
        print("  mustel clean cache     Remove pip cache")
        print("  mustel clean pycache   Remove __pycache__ in current folder")
        print("  mustel clean all       Remove everything")
        return
    
    if target == "cache":
        print("🧹 Clearing pip cache...\n")
        result = subprocess.run(
            [sys.executable, "-m", "pip", "cache", "purge"],
            capture_output=True,
            text=True
        )
        if result.returncode == 0:
            print("✅ Pip cache cleared!")
        else:
            print(f"❌ Failed: {result.stderr}")
    
    elif target == "pycache":
        print("🧹 Removing __pycache__ folders...\n")
        cwd = os.getcwd()
        removed = 0
        for root, dirs, files in os.walk(cwd):
            if '__pycache__' in dirs:
                cache_path = os.path.join(root, '__pycache__')
                try:
                    import shutil
                    shutil.rmtree(cache_path)
                    removed += 1
                    print(f"  ✅ {cache_path}")
                except Exception as e:
                    print(f"  ❌ {cache_path}: {e}")
        print(f"\n✅ Removed {removed} __pycache__ folders.")
    
    elif target == "all":
        print("🧹 Cleaning everything...\n")
        # Clean pip cache
        subprocess.run(
            [sys.executable, "-m", "pip", "cache", "purge"],
            capture_output=True
        )
        print("  ✅ Pip cache cleared")
        
        # Clean pycache
        cwd = os.getcwd()
        removed = 0
        for root, dirs, files in os.walk(cwd):
            if '__pycache__' in dirs:
                cache_path = os.path.join(root, '__pycache__')
                try:
                    import shutil
                    shutil.rmtree(cache_path)
                    removed += 1
                except Exception:
                    pass
        print(f"  ✅ Removed {removed} __pycache__ folders")
        print("\n✅ All done!")
    
    else:
        print(f"Unknown target: {target}")
        print("Use: cache, pycache, or all")


def cmd_doctor():
    """System health check for Python."""
    print("\n🦦 mustel doctor\n")
    print("⏳ Running health checks... please wait...\n")
    print("🔍 Checking your Python setup...\n")
    
    issues = []
    warnings = []
    
    # Check 1: Python version
    version = sys.version.split()[0]
    print(f"  🐍 Python: {version}")
    major, minor = sys.version_info[:2]
    if major < 3 or (major == 3 and minor < 8):
        issues.append("Python 3.8+ recommended")
    else:
        print("     ✅ Version is supported")
    
    # Check 2: pip exists and version
    pip_out = run_cmd([sys.executable, "-m", "pip", "--version"])
    if pip_out:
        pip_version = pip_out.split()[1]
        print(f"\n  📦 pip: {pip_version}")
        # Check if pip is outdated
        outdated = get_outdated(sys.executable)
        if 'pip' in outdated:
            old, new = outdated['pip']
            warnings.append(f"pip is outdated ({old} → {new})")
            print(f"     ⚠️  Update available: {new}")
        else:
            print("     ✅ Up to date")
    else:
        issues.append("pip not found!")
        print("\n  📦 pip: ❌ NOT FOUND")
    
    # Check 3: Multiple Pythons
    others = find_all_pythons()
    print(f"\n  🔢 Python installations: {len(others) + 1}")
    if len(others) > 2:
        warnings.append(f"You have {len(others) + 1} Python installations")
    else:
        print("     ✅ Looks clean")
    
    # Check 4: Current Python path
    print(f"\n  📍 Current Python: {sys.executable}")
    
    # Check 5: Is in venv?
    if sys.prefix != sys.base_prefix:
        print(f"\n  🌐 Virtual env: ACTIVE")
        print(f"     Path: {sys.prefix}")
    else:
        print(f"\n  🌐 Virtual env: Not active")
    
    # Check 6: PATH issues (Windows)
    if platform.system() == "Windows":
        path_env = os.environ.get('PATH', '')
        python_in_path = any('python' in p.lower() for p in path_env.split(';'))
        if not python_in_path:
            issues.append("Python not found in PATH")
    
    # Summary
    print("\n" + "─" * 50)
    
    if not issues and not warnings:
        print("\n✅ Everything looks good!")
    else:
        if warnings:
            print(f"\n⚠️  Warnings ({len(warnings)}):")
            for w in warnings:
                print(f"   • {w}")
        if issues:
            print(f"\n❌ Issues ({len(issues)}):")
            for i in issues:
                print(f"   • {i}")
    
    print()


def cmd_help():
    """Show help."""
    print(r"""
mustel — Python environment inspector

Commands:
  mustel             Show packages in current Python
  mustel all         Show all Python installations  
  mustel diff        Compare packages across Pythons
  mustel check X     Check if package X exists
  mustel install X   Install package X
  mustel updates     Show outdated packages
  mustel upgrade     Upgrade all outdated packages
  mustel upgrade --safe  Upgrade (skip major versions)
  mustel help        Show this help

Maintenance:
  mustel doctor      Health check for Python setup
  mustel clean       Show cleanable cache files
  mustel clean cache   Clear pip cache
  mustel clean pycache Clear __pycache__ folders
  mustel clean all     Clear everything

Virtual Environments:
  mustel venv        Venv status dashboard
  mustel venv new    Create a new venv
  mustel venv list   Find all venvs on system
  mustel venv destroy Delete the venv

Tip: Run with different Python:
  C:\path\python.exe -m mustel
""")


# ============================================================
# MAIN
# ============================================================

def main():
    args = sys.argv[1:]
    
    if not args:
        cmd_list()
    elif args[0] in ("help", "-h", "--help"):
        cmd_help()
    elif args[0] in ("version", "-v", "--version"):
        print(f"mustel {__version__}")
    elif args[0] == "all":
        cmd_all()
    elif args[0] == "diff":
        cmd_diff()
    elif args[0] == "check":
        if len(args) > 1:
            cmd_check(args[1])
        else:
            print("Usage: mustel check <package_name>")
    elif args[0] == "updates":
        cmd_updates()
    elif args[0] == "upgrade":
        safe_mode = "--safe" in args or "-s" in args
        cmd_upgrade(safe_mode)
    elif args[0] == "install":
        if len(args) > 1:
            cmd_install(args[1])
        else:
            print("Usage: mustel install <package_name>")
    elif args[0] == "clean":
        target = args[1] if len(args) > 1 else None
        cmd_clean(target)
    elif args[0] == "doctor":
        cmd_doctor()
    elif args[0] == "venv":
        from mustel.venv import handle_venv
        handle_venv(args[1:])
    else:
        print(f"Unknown command: {args[0]}")
        cmd_help()


if __name__ == "__main__":
    main()
