import os
import subprocess

import requests

try:
    from .version import VERSION
except ImportError:
    from version import VERSION


UPDATE_URL_JSON = os.environ.get("LIMRISTEM_WEB_UPDATE_URL", "https://get.limristem.eu/web/version.json")
TIMEOUT = 15


def check_for_updates():
    """
    Checks for updates by querying the remote metadata endpoint.
    Returns: (has_update, new_version_str, error_msg)
    """
    if not UPDATE_URL_JSON:
        return False, None, "Update metadata endpoint is not configured."
    try:
        response = requests.get(UPDATE_URL_JSON, timeout=TIMEOUT)
        if response.status_code != 200:
            return False, None, f"HTTP Error: {response.status_code}"

        try:
            data = response.json()
        except ValueError:
            return False, None, "Invalid JSON response"

        if isinstance(data, list) and len(data) > 0:
            data = data[0]
        elif not isinstance(data, dict):
            return False, None, "Invalid JSON structure"

        remote_version = data.get('version')
        if not remote_version:
            return False, None, "Invalid JSON: 'version' key missing"
        return is_newer(remote_version, VERSION), remote_version, None
    except requests.Timeout:
        return False, None, "Connection timed out (15s)"
    except requests.RequestException as e:
        return False, None, f"Connection error: {e}"


def is_newer(remote, local):
    remote = remote.lstrip('v')
    local = local.lstrip('v')

    try:
        from packaging.version import parse
        return parse(remote) > parse(local)
    except ImportError:
        pass

    try:
        r_parts = [int(x) for x in remote.split('.')]
        l_parts = [int(x) for x in local.split('.')]
        return r_parts > l_parts
    except Exception:
        return remote > local


def _resolve_app_dir():
    current_dir = os.path.dirname(os.path.abspath(__file__))
    if os.path.exists(os.path.join(current_dir, "install.sh")):
        return current_dir
    return os.path.dirname(current_dir)


def _git_pull_ff_only(app_dir):
    if not os.path.exists(os.path.join(app_dir, ".git")):
        return False, "Self-update is disabled for non-git installations."

    fetch = subprocess.run(
        ["git", "fetch", "--tags", "--prune"],
        cwd=app_dir,
        capture_output=True,
        text=True,
    )
    if fetch.returncode != 0:
        return False, fetch.stderr.strip() or fetch.stdout.strip() or "git fetch failed"

    pull = subprocess.run(
        ["git", "pull", "--ff-only"],
        cwd=app_dir,
        capture_output=True,
        text=True,
    )
    if pull.returncode != 0:
        return False, pull.stderr.strip() or pull.stdout.strip() or "git pull failed"

    return True, None


def _tarball_update(app_dir):
    if not UPDATE_URL_JSON:
        return False, "Update metadata endpoint is not configured."
    try:
        response = requests.get(UPDATE_URL_JSON, timeout=TIMEOUT)
        response.raise_for_status()
        data = response.json()
        remote_version = data.get('version')
        if not remote_version:
            return False, "Invalid JSON: 'version' key missing"
            
        archive_url = data.get('archive_url')
        if not archive_url:
            archive_url = f"https://get.limristem.eu/web/limristem-web-{remote_version}.tar.gz"

        import tempfile
        fd, archive_path = tempfile.mkstemp(suffix=".tar.gz")
        os.close(fd)
        
        try:
            curl_cmd = ["curl", "-sSL", archive_url, "-o", archive_path]
            res = subprocess.run(curl_cmd, capture_output=True, text=True)
            if res.returncode != 0:
                return False, f"Failed to download update: {res.stderr}"

            # Safety check: verify no path traversal entries in the archive
            list_res = subprocess.run(["tar", "-tzf", archive_path], capture_output=True, text=True)
            if list_res.returncode != 0:
                return False, "Failed to list archive entries"
            for entry in list_res.stdout.splitlines():
                if ".." in entry or entry.startswith("/"):
                    return False, f"Unsafe path in archive: {entry}"

            tar_cmd = ["tar", "-xzf", archive_path, "--no-same-owner", "--no-same-permissions", "-C", app_dir]
            res = subprocess.run(tar_cmd, capture_output=True, text=True)
            if res.returncode != 0:
                return False, f"Failed to extract update: {res.stderr}"
        finally:
            if os.path.exists(archive_path):
                os.remove(archive_path)
                
        return True, None
    except Exception as e:
        return False, f"Tarball update failed: {e}"


def perform_update():
    """
    Executes a tightly-scoped update process.
    1. Checks if it's a git checkout or tarball.
    2. Performs `git pull --ff-only` or tarball extraction.
    3. Runs `install.sh -U`.
    """
    app_dir = _resolve_app_dir()

    if os.path.exists(os.path.join(app_dir, ".git")):
        ok, err = _git_pull_ff_only(app_dir)
    else:
        ok, err = _tarball_update(app_dir)
        
    if not ok:
        return False, err

    install_script = os.path.join(app_dir, "install.sh")
    if not os.path.exists(install_script):
        return False, "Update fetched but install.sh was not found."

    try:
        subprocess.run(["chmod", "+x", install_script], check=True)
        log_file = open("/var/log/limristem_update.log", "a")
        subprocess.Popen(
            ["bash", install_script, "-U"], 
            cwd=app_dir, 
            start_new_session=True,
            stdout=log_file,
            stderr=subprocess.STDOUT,
            stdin=subprocess.DEVNULL
        )
        # We don't close log_file here; the OS will clean it up on process exit, 
        # and Popen dup'd it for the child. (Actually, it's safer to not close it explicitly if we want it to stay open, though Popen does dup it).
        return True, "Update downloaded. Installation script started in background. Check /var/log/limristem_update.log"
    except Exception as e:
        return False, f"Failed to start installer: {e}"
