import subprocess
import sys
import json


def verify_install(package_name: str) -> dict:
    """
    Verify a package is actually importable after installation.
    Runs in a subprocess to get a clean import environment.
    Returns a result dict with importable, version, and error if any.
    """
    # Normalize package name to import name
    import_name = _resolve_import_name(package_name)

    probe = f"""
import sys
import json

try:
    mod = __import__("{import_name}")
    version = getattr(mod, "__version__", None)
    print(json.dumps({{"importable": True, "version": version}}))
except ImportError as e:
    print(json.dumps({{"importable": False, "error": str(e)}}))
except Exception as e:
    print(json.dumps({{"importable": False, "error": f"Unexpected error: {{e}}"}}))
"""

    try:
        result = subprocess.run(
            [sys.executable, "-c", probe],
            capture_output=True,
            text=True,
            timeout=15,
        )

        if result.returncode != 0 or not result.stdout.strip():
            return {
                "importable": False,
                "error": result.stderr.strip() or "No output from import probe",
            }

        return json.loads(result.stdout.strip())

    except subprocess.TimeoutExpired:
        return {"importable": False, "error": "Import probe timed out after 15s"}
    except json.JSONDecodeError:
        return {"importable": False, "error": f"Could not parse probe output: {result.stdout.strip()}"}
    except Exception as e:
        return {"importable": False, "error": str(e)}


def triage_import_failure(package_name: str) -> dict:
    """
    When verify_install fails, attempt to diagnose why.
    Returns a diagnosis dict with cause and suggested fix.
    """
    import_name = _resolve_import_name(package_name)

    checks = {
        "missing_entirely": _check_missing(package_name),
        "wrong_python": _check_wrong_python(package_name),
        "broken_deps": _check_broken_deps(package_name),
        "c_extension_broken": _check_c_extension(import_name),
    }

    # Determine the most likely cause
    if checks["missing_entirely"]:
        cause = "Package is not installed at all"
        fix = f"peep install {package_name}"
    elif checks["wrong_python"]:
        cause = "Package was installed for a different Python interpreter"
        fix = f"peep install {package_name}  (ensure you're using the right python)"
    elif checks["broken_deps"]:
        cause = f"Missing or broken dependencies: {checks['broken_deps']}"
        fix = f"peep install {package_name} --force"
    elif checks["c_extension_broken"]:
        cause = "C extension failed to load (likely a build or ABI issue)"
        fix = f"peep install {package_name} --force --no-binary"
    else:
        cause = "Unknown import failure"
        fix = f"peep install {package_name} --force"

    return {
        "package": package_name,
        "cause": cause,
        "suggested_fix": fix,
        "checks": checks,
    }


def _check_missing(package_name: str) -> bool:
    """Check if the package shows up in pip list at all."""
    result = subprocess.run(
        [sys.executable, "-m", "pip", "show", package_name],
        capture_output=True, text=True
    )
    return result.returncode != 0


def _check_wrong_python(package_name: str) -> bool:
    """
    Check if the package is installed but under a different Python.
    Detects the common case of pip vs pip3 confusion.
    """
    result = subprocess.run(
        [sys.executable, "-m", "pip", "show", package_name],
        capture_output=True, text=True
    )
    if result.returncode != 0:
        return False

    # Check if the install location is within the current sys.prefix
    for line in result.stdout.splitlines():
        if line.startswith("Location:"):
            location = line.split(":", 1)[1].strip()
            return sys.prefix.lower() not in location.lower()

    return False


def _check_broken_deps(package_name: str) -> list[str] | None:
    """Use pip check to find broken dependencies."""
    result = subprocess.run(
        [sys.executable, "-m", "pip", "check"],
        capture_output=True, text=True
    )
    if result.returncode == 0:
        return None

    # Filter output to lines mentioning this package
    broken = [
        line for line in result.stdout.splitlines()
        if package_name.lower() in line.lower()
    ]
    return broken if broken else None


def _check_c_extension(import_name: str) -> bool:
    """Check if the failure is specifically from a C extension load error."""
    probe = f"""
try:
    __import__("{import_name}")
except ImportError as e:
    print("c_ext" if "cannot import" in str(e).lower() or ".so" in str(e) else "other")
except Exception:
    print("other")
else:
    print("ok")
"""
    result = subprocess.run(
        [sys.executable, "-c", probe],
        capture_output=True, text=True,
        timeout=10,
    )
    return result.stdout.strip() == "c_ext"


# Map of PyPI package names that differ from their import names
_IMPORT_NAME_MAP = {
    "pillow": "PIL",
    "opencv-python": "cv2",
    "opencv-python-headless": "cv2",
    "scikit-learn": "sklearn",
    "scikit-image": "skimage",
    "beautifulsoup4": "bs4",
    "pyyaml": "yaml",
    "python-dateutil": "dateutil",
    "python-dotenv": "dotenv",
    "pyzmq": "zmq",
    "pygments": "pygments",
    "typing-extensions": "typing_extensions",
    "attrs": "attr",
    "moviepy": "moviepy.editor",
}


def _resolve_import_name(package_name: str) -> str:
    """Resolve PyPI package name to its actual Python import name."""
    normalized = package_name.lower().strip()
    return _IMPORT_NAME_MAP.get(normalized, normalized.replace("-", "_"))