#!/bin/bash

set -euo pipefail

# Cleanup temp files on exit
_tmpfile=""
_cleanup ()
{
    if [[ -n "${_tmpfile}" && -f "${_tmpfile}" ]]; then
        rm -f "${_tmpfile}"
    fi
}
trap _cleanup EXIT

_require_root ()
{
    if [[ $EUID -ne 0 ]]; then
        echo "This script must be run as root." >&2
        # $0 is unreliable here: with process substitution (bash <(curl ...))
        # it points at /dev/fd/NN, which sudo cannot reopen in its own process.
        # With a plain file it's a usable path.
        case "$0" in
            /dev/fd/*|/proc/self/fd/*|bash|-bash|*/bash)
                echo "Try:" >&2
                echo "  curl -fsSL https://gameap.com/install.sh -o install.sh && sudo bash install.sh" >&2
                ;;
            *)
                echo "Try: sudo bash $0" >&2
                ;;
        esac
        exit 1
    fi
}

_unknown_os ()
{
    echo "Unfortunately, your operating system distribution and version are not supported by this script." >&2
    exit 2
}

_unknown_arch ()
{
    echo "Unfortunately, your architecture is not supported by this script." >&2
    exit 2
}

_detect_os ()
{
    os=""
    dist=""
    os_like=""

    if [[ -e /etc/os-release ]]; then
        . /etc/os-release

        os="${ID:-}"
        os_like="${ID_LIKE:-}"

        if [[ -n "${VERSION_CODENAME:-}" ]]; then
            dist=${VERSION_CODENAME:-}
        elif [[ -n "${VERSION_ID:-}" ]]; then
            dist=${VERSION_ID:-}
        elif [[ -n "${BUILD_ID:-}" ]]; then
            # Rolling-release distributions (Arch, Manjaro, CachyOS,
            # EndeavourOS, ...) carry no version number; os-release
            # exposes BUILD_ID (typically "rolling") instead.
            dist=${BUILD_ID:-}
        fi

        # ID_LIKE fallback: treat rhel/rocky/almalinux/fedora uniformly where possible
        if [[ -z "${os}" && -n "${ID_LIKE:-}" ]]; then
            os="${ID_LIKE%% *}"
        fi
    elif [[ -e /etc/lsb-release ]]; then
        . /etc/lsb-release

        if [[ "${ID:-}" = "raspbian" ]]; then
            os=${ID}
            dist=$(cut --delimiter='.' -f1 /etc/debian_version)
        else
            os=${DISTRIB_ID:-}
            dist=${DISTRIB_CODENAME:-}

            if [ -z "$dist" ]; then
                dist=${DISTRIB_RELEASE:-}
            fi
        fi
    elif command -v lsb_release > /dev/null 2>&1; then
        dist=$(lsb_release -c | cut -f2)
        os=$(lsb_release -i | cut -f2 | awk '{ print tolower($1) }')
    fi

    if [[ -z "$dist" ]] && [[ -e /etc/debian_version ]]; then
        os=$(head -1 /etc/issue | awk '{ print tolower($1) }')
        if grep -q '/' /etc/debian_version; then
            dist=$(cut --delimiter='/' -f1 /etc/debian_version)
        else
            dist=$(cut --delimiter='.' -f1 /etc/debian_version)
        fi
    fi

    if [[ "${os}" = "debian" ]]; then
        case $dist in
            6* ) dist="squeeze" ;;
            7* ) dist="wheezy" ;;
            8* ) dist="jessie" ;;
            9* ) dist="stretch" ;;
            10* ) dist="buster" ;;
            11* ) dist="bullseye" ;;
            12* ) dist="bookworm" ;;
            13* ) dist="trixie" ;;
        esac
    fi

    # Rolling-release / Arch-like distributions don't carry a version.
    # If we still have no dist (e.g. detected via lsb_release, or a minimal
    # os-release without BUILD_ID), accept it as "rolling" so the guard
    # below doesn't reject an otherwise supported Arch derivative.
    if [[ -z "$dist" ]]; then
        case "${os,,}" in
            arch|manjaro|cachyos|endeavouros|artix|garuda|arcolinux|archcraft)
                dist="rolling"
                ;;
            *)
                case " ${os_like,,} " in
                    *" arch "*) dist="rolling" ;;
                esac
                ;;
        esac
    fi

    if [[ -z "$dist" ]]; then
        _unknown_os
    fi

    # remove whitespace and lowercase
    os="${os// /}"
    dist="${dist// /}"
    os=${os,,}
    dist=${dist,,}

    echo "Detected operating system as $os/$dist."
}

cpuarch=""
os_like=""

_detect_arch ()
{
    local architecture
    architecture=$(uname -m)
    if [[ "$architecture" == x86_64* ]]; then
        cpuarch="amd64"
    elif [[ "$architecture" == i*86 ]]; then
        cpuarch="386"
    elif [[ "$architecture" == aarch64 ]]; then
        cpuarch="arm64"
    elif [[ "$architecture" == arm64 ]]; then
        cpuarch="arm64"
    elif [[ "$architecture" == arm* ]]; then
        cpuarch="arm"
    fi

    if [[ -z "$cpuarch" ]]; then
        _unknown_arch
    fi
}

# Pick a package manager based on detected OS / available binaries.
# Echoes one of: apt, dnf, yum, zypper, pacman, apk — or empty on unknown.
_detect_pm ()
{
    case "${os}" in
        debian|ubuntu|raspbian|linuxmint|pop)
            echo "apt"
            ;;
        centos|rhel|rocky|almalinux|ol|oraclelinux|fedora|amzn)
            if command -v dnf > /dev/null; then
                echo "dnf"
            else
                echo "yum"
            fi
            ;;
        opensuse*|sles|suse)
            echo "zypper"
            ;;
        arch|manjaro|cachyos|endeavouros|artix|garuda|arcolinux|archcraft)
            echo "pacman"
            ;;
        alpine)
            echo "apk"
            ;;
        *)
            # Use ID_LIKE to map derivative distros onto their base family
            # before falling back to probing for package-manager binaries.
            case " ${os_like} " in
                *" arch "*)
                    echo "pacman"; return ;;
                *" debian "*|*" ubuntu "*)
                    echo "apt"; return ;;
                *" rhel "*|*" fedora "*|*" centos "*)
                    if command -v dnf > /dev/null; then
                        echo "dnf"
                    else
                        echo "yum"
                    fi
                    return ;;
                *" suse "*)
                    echo "zypper"; return ;;
            esac

            # Best-effort fallback by binary presence
            for pm in apt-get dnf yum zypper pacman apk; do
                if command -v "$pm" > /dev/null; then
                    case "$pm" in
                        apt-get) echo "apt"; return ;;
                        *) echo "${pm}"; return ;;
                    esac
                fi
            done
            echo ""
            ;;
    esac
}

_install_pkg ()
{
    local pkg="$1"
    local pm
    pm=$(_detect_pm)

    if [[ -z "${pm}" ]]; then
        echo "Unsupported OS: no known package manager found for '${os}'." >&2
        echo "Please install '${pkg}' manually and re-run this script." >&2
        exit 1
    fi

    echo "Installing ${pkg} via ${pm}..."

    case "${pm}" in
        apt)
            if ! apt-get -y update > /dev/null; then
                echo "apt-get update failed." >&2
                exit 1
            fi
            if ! apt-get install -q -y "${pkg}" > /dev/null; then
                echo "Unable to install ${pkg} via apt-get." >&2
                exit 1
            fi
            ;;
        dnf)
            if ! dnf -q -y install "${pkg}" > /dev/null; then
                echo "Unable to install ${pkg} via dnf." >&2
                exit 1
            fi
            ;;
        yum)
            if ! yum -q -y install "${pkg}" > /dev/null; then
                echo "Unable to install ${pkg} via yum." >&2
                exit 1
            fi
            ;;
        zypper)
            if ! zypper --non-interactive install "${pkg}" > /dev/null; then
                echo "Unable to install ${pkg} via zypper." >&2
                exit 1
            fi
            ;;
        pacman)
            if ! pacman -Sy --noconfirm "${pkg}" > /dev/null; then
                echo "Unable to install ${pkg} via pacman." >&2
                exit 1
            fi
            ;;
        apk)
            if ! apk add --no-cache "${pkg}" > /dev/null; then
                echo "Unable to install ${pkg} via apk." >&2
                exit 1
            fi
            ;;
        *)
            echo "Unsupported package manager: ${pm}" >&2
            exit 1
            ;;
    esac
}

_curl_check ()
{
    echo "Checking for curl..."
    if command -v curl > /dev/null; then
        echo "Detected curl."
        return
    fi
    _install_pkg curl
}

_tar_check ()
{
    echo "Checking for tar..."
    if command -v tar > /dev/null; then
        echo "Detected tar."
        return
    fi
    _install_pkg tar
}

_require_root
_detect_os
_detect_arch

gameapctl_version="0.6.0"
gameapctl_archive="gameapctl-v${gameapctl_version}-linux-${cpuarch}.tar.gz"
gameapctl_url="https://github.com/gameap/gameapctl/releases/download/v${gameapctl_version}/${gameapctl_archive}"

echo "Preparation for installation..."
_curl_check
_tar_check

if ! command -v gameapctl > /dev/null; then
    echo
    echo "Downloading gameapctl for your operating system..."

    _tmpfile="$(mktemp -t gameapctl.XXXXXX.tar.gz)"
    if ! curl -fsSL "${gameapctl_url}" --output "${_tmpfile}"; then
        echo "Failed to download gameapctl from ${gameapctl_url}" >&2
        exit 1
    fi

    echo
    echo "Unpacking archive..."

    # Extract to a temp dir first, then move only the expected binary into /usr/local/bin.
    # This avoids writing arbitrary archive contents into a system path.
    _tmpdir="$(mktemp -d -t gameapctl.XXXXXX)"
    if ! tar -xf "${_tmpfile}" -C "${_tmpdir}"; then
        echo "Failed to unpack ${_tmpfile}" >&2
        rm -rf "${_tmpdir}"
        exit 1
    fi

    if [[ ! -f "${_tmpdir}/gameapctl" ]]; then
        echo "Archive does not contain expected 'gameapctl' binary." >&2
        rm -rf "${_tmpdir}"
        exit 1
    fi

    install -m 0755 "${_tmpdir}/gameapctl" /usr/local/bin/gameapctl
    rm -rf "${_tmpdir}"
fi

# Make sure /usr/local/bin is in PATH (some sudo/secure_path setups strip it)
# and drop any cached lookup of 'gameapctl' from before it was installed.
case ":${PATH}:" in
    *:/usr/local/bin:*) ;;
    *) export PATH="/usr/local/bin:${PATH}" ;;
esac
hash -r

# Resolve the binary explicitly so subsequent calls don't depend on PATH/hash.
gameapctl_bin="$(command -v gameapctl || true)"
if [[ -z "${gameapctl_bin}" ]]; then
    if [[ -x /usr/local/bin/gameapctl ]]; then
        gameapctl_bin="/usr/local/bin/gameapctl"
    else
        echo "gameapctl was not found after installation. Expected at /usr/local/bin/gameapctl." >&2
        exit 1
    fi
fi

echo
echo "gameapctl updating..."
"${gameapctl_bin}" self-update

echo
echo "Running installation..."
# gameapctl's interactive panel installer requires a real controlling TTY.
# When this script is piped (e.g. `curl ... | sudo bash`), stdin is the pipe,
# and reattaching /dev/tty manually is not enough for some TUI libraries —
# they probe the terminal in ways that bash redirection cannot fully emulate.
# Rather than silently hang or exit, refuse early with a clear instruction.
#
# Exception: if the user passed installation arguments ("$@" non-empty), the
# installer can run non-interactively, so a missing TTY is fine.
if [[ $# -eq 0 && ! -t 0 ]]; then
    echo "" >&2
    echo "ERROR: interactive installation requires a terminal on stdin." >&2
    echo "It looks like this script was piped (e.g. 'curl ... | sudo bash')," >&2
    echo "which does not provide a TTY to the installer." >&2
    echo "" >&2
    echo "Please use one of the following instead:" >&2
    echo "" >&2
    echo "  # Download, then run:" >&2
    echo "  curl -fsSL https://gameap.com/install.sh -o install.sh && sudo bash install.sh" >&2
    echo "" >&2
    echo "  # Or run non-interactively by passing parameters:" >&2
    echo "  curl -fsSL https://gameap.com/install.sh | sudo bash -s -- --host=127.0.0.1 --path=/var/www/gameap" >&2
    echo "" >&2
    exit 1
fi

"${gameapctl_bin}" panel install "$@"
