from __future__ import annotations

import argparse
import json
import subprocess
import sys
import urllib.request
from pathlib import Path

from .engine import SOLAR_VERSION, run_file, compile_to_python

# IMPORTANT: this is the *PyPI project name* you publish under (can be different than command name)
PYPIPKG = "solar-lang"
PYPI_JSON_URL = f"https://pypi.org/pypi/{PYPIPKG}/json"


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(prog="solar")
    sub = p.add_subparsers(dest="cmd", required=True)

    r = sub.add_parser("run", help="Run a .solar script")
    r.add_argument("file", help="Path to .solar file")

    c = sub.add_parser("compile", help="Compile .solar to a standalone .py script")
    c.add_argument("file", help="Path to .solar file")
    c.add_argument("-o", "--out", help="Output .py path (default: same name with .py)")

    sub.add_parser("version", help="Show Solar version")
    sub.add_parser("help", help="Show help")

    u = sub.add_parser("update", help="Check for updates and upgrade Solar via pip")
    u.add_argument("--user", action="store_true", help="Force --user install (useful outside venv)")

    return p


def _parse_version_tuple(v: str) -> tuple:
    """
    Tiny version parser for comparisons without adding dependencies.
    Works for common versions like:
      - 1.2.3
      - 1.2.3rc1
      - 1.2.3.post1
    Not a full PEP 440 implementation, but good enough for typical PyPI 'info.version'.
    """
    v = v.strip()
    parts: list[object] = []
    buf = ""

    for ch in v:
        if ch.isdigit():
            buf += ch
            continue

        if buf:
            parts.append(int(buf))
            buf = ""

        # keep some signal for prerelease/post tags
        if ch.isalpha():
            parts.append(ch.lower())

    if buf:
        parts.append(int(buf))

    return tuple(parts)


def _get_latest_pypi_version(timeout_s: float = 1.5) -> str | None:
    """
    Returns the latest version string from PyPI, or None if unavailable.
    Uses PyPI JSON API: https://pypi.org/pypi/<project>/json
    """
    try:
        req = urllib.request.Request(
            PYPI_JSON_URL,
            headers={"User-Agent": f"solar/{SOLAR_VERSION} (update-check)"},
        )
        with urllib.request.urlopen(req, timeout=timeout_s) as resp:
            data = json.loads(resp.read().decode("utf-8"))
        v = str(data.get("info", {}).get("version", "")).strip()
        return v or None
    except Exception:
        return None


def _maybe_print_update_notice() -> None:
    """
    Checks PyPI for the newest version and prints a notice if an update exists.
    Never raises; failures are silent.
    """
    latest = _get_latest_pypi_version()
    if not latest:
        return

    try:
        if _parse_version_tuple(latest) <= _parse_version_tuple(SOLAR_VERSION):
            return
    except Exception:
        # If parsing/comparison fails, don't block normal usage.
        return

    print(
        "Hey! There's a update available. The newest version is: "
        f"{latest}, but you are on: {SOLAR_VERSION}. "
        "Run 'solar update' to update to the latest version of Solar Lang!"
    )


def main(argv: list[str] | None = None) -> int:
    if argv is None:
        argv = sys.argv[1:]

    parser = build_parser()
    ns = parser.parse_args(argv)

    # Check for updates on every command (except 'update' itself to avoid redundancy).
    # If you truly want it on *every* command including 'update', remove this guard.
    if ns.cmd != "update":
        _maybe_print_update_notice()

    if ns.cmd == "run":
        p = Path(ns.file)
        if not p.exists():
            print(f"solar: file not found: {p}", file=sys.stderr)
            return 2
        run_file(p)
        return 0

    if ns.cmd == "compile":
        p = Path(ns.file)
        if not p.exists():
            print(f"solar: file not found: {p}", file=sys.stderr)
            return 2
        out = compile_to_python(p, ns.out)
        print(out)
        return 0

    if ns.cmd == "version":
        print(SOLAR_VERSION)
        return 0

    if ns.cmd == "help":
        parser.print_help()
        return 0

    if ns.cmd == "update":
        return _cmd_update(force_user=bool(ns.user))

    parser.print_help()
    return 2


def _is_linux() -> bool:
    return sys.platform.startswith("linux")


def _in_venv() -> bool:
    # Works for venv + virtualenv
    return (getattr(sys, "base_prefix", sys.prefix) != sys.prefix) or bool(getattr(sys, "real_prefix", False))


def _cmd_update(force_user: bool) -> int:
    """
    'solar update' runs pip upgrade.
    On Debian/Ubuntu (PEP 668 externally-managed), pip may require --break-system-packages.
    We add it automatically on Linux when doing a non-venv install (or when forcing --user).
    """
    in_venv = _in_venv()

    cmd = [sys.executable, "-m", "pip", "install", "--upgrade", PYPIPKG]

    # If not in venv, default to --user unless they didn't want it
    using_user = False
    if force_user or (not in_venv):
        cmd.append("--user")
        using_user = True

    # On Linux systems with PEP 668, this prevents "externally-managed-environment"
    # Only add it when we're likely installing outside a venv (system/user context).
    if _is_linux() and (not in_venv or using_user):
        cmd.append("--break-system-packages")

    print("Running:", " ".join(cmd))
    try:
        proc = subprocess.run(cmd, check=False)
        return int(proc.returncode)
    except FileNotFoundError:
        print("solar: pip not found (is your Python install broken?)", file=sys.stderr)
        return 2


if __name__ == "__main__":
    raise SystemExit(main())
