#!/bin/sh
# ANIMAIL Linux beta. Run as your normal desktop user.
# The final main call keeps a partial pipe download from beginning installation.
set -eu
PATH=/usr/bin:/bin:/usr/sbin:/sbin
export PATH

ANIMAIL_WEBSITE='https://animail.app'
ANIMAIL_DOWNLOAD_ORIGIN='https://messenger.asiadesignlab.com/releases/'
ANIMAIL_KEY_FINGERPRINT='f4227061162302d9eb92135ed378b85680cf2922758deadaac435145296593b6'
ANIMAIL_OS_RELEASE='/etc/os-release'
ANIMAIL_WORK=''

fail() { printf '\nANIMAIL: %s\n' "$*" >&2; exit 1; }
cleanup() {
    case "$ANIMAIL_WORK" in
        /tmp/animail-install.*) rm -rf -- "$ANIMAIL_WORK" ;;
    esac
}
download() {
    if command -v curl >/dev/null 2>&1; then
        curl --fail --show-error --proto '=https' --tlsv1.2 --max-redirs 0 \
            --connect-timeout 20 --max-time 600 --max-filesize "$3" \
            --output "$2" "$1"
    else
        wget --https-only --max-redirect=0 --timeout=30 --tries=2 --output-document="$2" "$1" || return 1
        [ "$(wc -c < "$2")" -le "$3" ] || fail 'The download is larger than expected.'
    fi
}

main() {
    mode=install
    case "${1-}" in
        '') [ "$#" -eq 0 ] || fail 'Unrecognized arguments.' ;;
        --verify-only) [ "$#" -eq 1 ] || fail 'Unrecognized arguments.'; mode=verify ;;
        --help) printf '%s\n' 'Usage: sh install-linux.sh [--verify-only]' 'Run as your normal desktop user: Debian 13 amd64 Xfce/X11, or Ubuntu 24.04 amd64 GNOME with X11/XWayland.'; return ;;
        *) fail 'Usage: sh install-linux.sh [--verify-only]' ;;
    esac
    [ "$(id -u)" -ne 0 ] || fail 'Run this command as your normal desktop user, without sudo. Administrator access is requested only after verification.'
    for tool in python3 openssl sha256sum dpkg dpkg-deb dpkg-query mktemp wc sed pgrep; do
        command -v "$tool" >/dev/null 2>&1 || fail "Missing $tool. Install the standard tools first: sudo apt install ca-certificates curl python3 openssl procps"
    done
    if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
        fail 'Install a downloader first: sudo apt install ca-certificates curl'
    fi
    ANIMAIL_TARGET=$(python3 -I - "$ANIMAIL_OS_RELEASE" <<'PY'
import pathlib,shlex,subprocess,sys
values={}
for line in pathlib.Path(sys.argv[1]).read_text().splitlines():
    if '=' not in line or line.startswith('#'): continue
    key,value=line.split('=',1)
    if key in ('ID','VERSION_ID'):
        if key in values: raise SystemExit('ANIMAIL: duplicate operating-system identity fields.')
        parsed=shlex.split(value)
        values[key]=parsed[0] if len(parsed)==1 else ''
if subprocess.check_output(['dpkg','--print-architecture'],text=True).strip()!='amd64':
    raise SystemExit('ANIMAIL: this beta requires amd64 (x86-64).')
identity=(values.get('ID'),values.get('VERSION_ID'))
if identity==('debian','13'): print('debian-13')
elif identity==('ubuntu','24.04'): print('ubuntu-24.04')
else: raise SystemExit('ANIMAIL: this beta supports Debian 13 or Ubuntu 24.04, using their exact operating-system IDs.')
PY
    ) || fail 'The supported Debian 13 or Ubuntu 24.04 amd64 platform could not be confirmed. See https://animail.app/linux/.'
    desktop=$(printf '%s' "${XDG_CURRENT_DESKTOP-}" | tr '[:lower:]' '[:upper:]')
    case "$ANIMAIL_TARGET" in
        debian-13)
            ANIMAIL_MANIFEST='/linux-manifest.json'
            [ "${XDG_SESSION_TYPE-}" = x11 ] && [ -n "${DISPLAY-}" ] || fail 'Debian 13 requires an Xfce X11 desktop terminal. Wayland is not supported for this target.'
            case ":$desktop:" in *:XFCE:*) ;; *) fail 'The Debian target requires the Xfce desktop. See https://animail.app/linux/.' ;; esac
            ;;
        ubuntu-24.04)
            ANIMAIL_MANIFEST='/linux-manifest-ubuntu-24.04.json'
            case "${XDG_SESSION_TYPE-}" in x11|wayland) ;; *) fail 'Ubuntu 24.04 requires a GNOME X11 or Wayland desktop terminal.' ;; esac
            [ -n "${DISPLAY-}" ] || fail 'Ubuntu GNOME requires an X11/XWayland DISPLAY for the embedded editor. Open a terminal in that desktop session.'
            case ":$desktop:" in *:GNOME:*) ;; *) fail 'The Ubuntu 24.04 target requires the GNOME desktop. See https://animail.app/linux/.' ;; esac
            ;;
        *) fail 'Unsupported Linux target.' ;;
    esac
    if [ "$mode" = install ]; then
        command -v sudo >/dev/null 2>&1 && [ -x /usr/bin/apt ] || fail 'Installation requires apt and a desktop account with sudo access.'
        ( : < /dev/tty ) 2>/dev/null || fail 'Open an interactive desktop terminal so apt can ask for confirmation.'
    fi
    umask 077
    ANIMAIL_WORK=$(mktemp -d /tmp/animail-install.XXXXXXXX)
    trap cleanup 0
    trap 'exit 129' HUP
    trap 'exit 130' INT
    trap 'exit 143' TERM
    printf '%s\n' 'ANIMAIL Linux beta — verifying the official release.'
    cat > "$ANIMAIL_WORK/public-key.pem" <<'ANIMAIL_PUBLIC_KEY'
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAjwDLKZJWekHLKuRvjAr30jY6pptsVm7rJW554f6FW7w=
-----END PUBLIC KEY-----
ANIMAIL_PUBLIC_KEY
    openssl pkey -pubin -in "$ANIMAIL_WORK/public-key.pem" -outform DER -out "$ANIMAIL_WORK/public-key.der" 2>/dev/null || fail 'The embedded release key could not be read.'
    actual_key=$(sha256sum "$ANIMAIL_WORK/public-key.der")
    [ "${actual_key%% *}" = "$ANIMAIL_KEY_FINGERPRINT" ] || fail 'The Linux release key fingerprint differs.'
    download "$ANIMAIL_WEBSITE$ANIMAIL_MANIFEST" "$ANIMAIL_WORK/manifest.json" 65536 || fail 'Could not download the release manifest. Check your connection and retry.'
    download "$ANIMAIL_WEBSITE$ANIMAIL_MANIFEST.sig" "$ANIMAIL_WORK/manifest.sig" 64 || fail 'Could not download the release signature. Check your connection and retry.'
    [ "$(wc -c < "$ANIMAIL_WORK/manifest.sig")" -eq 64 ] || fail 'The release signature has an invalid size.'
    openssl pkeyutl -verify -pubin -inkey "$ANIMAIL_WORK/public-key.pem" -rawin \
        -in "$ANIMAIL_WORK/manifest.json" -sigfile "$ANIMAIL_WORK/manifest.sig" >/dev/null 2>&1 || fail 'Release signature verification failed. No installation was attempted; please retry or contact support.'
    python3 -I - "$ANIMAIL_WORK/manifest.json" "$ANIMAIL_WORK/fields" "$ANIMAIL_DOWNLOAD_ORIGIN" "$ANIMAIL_KEY_FINGERPRINT" "$ANIMAIL_TARGET" <<'PY' || fail 'Release metadata validation failed. No installation was attempted.'
import json,pathlib,re,sys,urllib.parse
def unique(pairs):
    result={}
    for key,value in pairs:
        if key in result: raise ValueError('Duplicate manifest key')
        result[key]=value
    return result
try:
    m=json.loads(pathlib.Path(sys.argv[1]).read_text(),object_pairs_hook=unique)
    assert type(m['schemaVersion']) is int and m['schemaVersion']==1
    assert (m['product'],m['channel'],m['platform'],m['runtime'],m['distribution'])==('ANIMAIL','beta','linux','linux-x64','deb')
    requirements=m['requirements']
    if sys.argv[5]=='debian-13':
        pattern=r'(\d+\.\d+\.\d+)~linux\.(\d+)'
        assert requirements['distribution']=='Debian GNU/Linux 13'
        assert requirements.get('osId','debian')=='debian' and requirements.get('osVersionId','13')=='13'
        assert requirements['desktop']=='Xfce' and requirements['displayServer']=='X11'
    elif sys.argv[5]=='ubuntu-24.04':
        pattern=r'(\d+\.\d+\.\d+)~ubuntu24\.04\.([1-9]\d*)'
        assert requirements['osId']=='ubuntu' and requirements['osVersionId']=='24.04'
        assert requirements['distribution']=='Ubuntu 24.04 LTS'
        assert requirements['desktop']=='GNOME 46' and requirements['displayServer']=='X11 or Wayland with XWayland'
    else: raise ValueError('Unsupported manifest target')
    assert requirements['architecture']=='amd64 (x86-64)'
    version=m['packageVersion'];match=re.fullmatch(pattern,version)
    assert match and m['version']==match[1] and len(version)<80
    a=m['assets']['package'];name='animail-linux_'+version+'_amd64.deb'
    assert a['name']==name and a['url']==sys.argv[3]+name
    u=urllib.parse.urlsplit(a['url'])
    assert u.scheme=='https' and not u.username and not u.password and not u.query and not u.fragment
    assert type(a['size']) is int and 0<a['size']<2147483648
    assert re.fullmatch(r'[a-f0-9]{64}',a['sha256'])
    assert m['signing']['algorithm']=='Ed25519' and m['signing']['keyId']=='sha256:'+sys.argv[4]
    assert m['signing']['publicKeyFingerprintSha256']==sys.argv[4]
    pathlib.Path(sys.argv[2]).write_text('\n'.join([version,name,a['url'],str(a['size']),a['sha256']])+'\n')
except (AssertionError,KeyError,ValueError,TypeError,IndexError):
    raise SystemExit('ANIMAIL: the signed release metadata does not match the supported Linux package. No installation was attempted.')
PY
    version=$(sed -n '1p' "$ANIMAIL_WORK/fields")
    name=$(sed -n '2p' "$ANIMAIL_WORK/fields")
    url=$(sed -n '3p' "$ANIMAIL_WORK/fields")
    size=$(sed -n '4p' "$ANIMAIL_WORK/fields")
    expected_hash=$(sed -n '5p' "$ANIMAIL_WORK/fields")
    if [ "$mode" = install ]; then
        data_home=${XDG_DATA_HOME:-"$HOME/.local/share"}
        if [ -e "$HOME/.local/bin/animail" ] || [ -L "$HOME/.local/bin/animail" ] || [ -e "$data_home/applications/animail.desktop" ]; then
            fail 'An existing per-user ANIMAIL launcher would override this system installation. Keep your mail and keyring, and contact support through https://animail.app/linux/ before migrating it.'
        fi
        installed_record=$(dpkg-query -W -f='${db:Status-Status}\n${Version}' animail-linux 2>/dev/null || :)
        installed_status=$(printf '%s\n' "$installed_record" | sed -n '1p')
        installed=$(printf '%s\n' "$installed_record" | sed -n '2p')
        if [ -n "$installed" ] && dpkg --compare-versions "$installed" gt "$version"; then
            fail 'A newer ANIMAIL package is already installed; this installer will not downgrade it.'
        fi
        if [ "$installed_status" = installed ] && [ -n "$installed" ] && dpkg --compare-versions "$installed" eq "$version"; then
            printf 'ANIMAIL %s is already installed. Open it from your applications menu.\n' "$version"; return
        fi
        if pgrep -x ANIMAIL.Native >/dev/null 2>&1; then
            fail 'ANIMAIL is running. Right-click its tray icon, choose Exit ANIMAIL, then run this command again.'
        fi
    fi
    printf 'Downloading ANIMAIL %s...\n' "$version"
    package="$ANIMAIL_WORK/$name"
    download "$url" "$package" "$size" || fail 'The package download failed. No installation was attempted.'
    [ "$(wc -c < "$package")" -eq "$size" ] || fail 'The package download has an unexpected size.'
    actual_hash=$(sha256sum "$package")
    [ "${actual_hash%% *}" = "$expected_hash" ] || fail 'Package checksum verification failed. No installation was attempted.'
    [ "$(dpkg-deb --field "$package" Package)" = animail-linux ] && \
    [ "$(dpkg-deb --field "$package" Version)" = "$version" ] && \
    [ "$(dpkg-deb --field "$package" Architecture)" = amd64 ] || fail 'The Debian package identity differs from the signed release.'
    python3 -I - "$package" "$ANIMAIL_TARGET" <<'PY' || fail 'The package dependencies do not match the selected distribution. No installation was attempted.'
import re,subprocess,sys
try:
    depends=subprocess.check_output(['dpkg-deb','--field',sys.argv[1],'Depends'],text=True).strip()
    groups=[]
    for group in depends.split(','):
        alternatives=[]
        for alternative in group.split('|'):
            match=re.fullmatch(r'\s*([a-z0-9][a-z0-9+.-]*)(?::[a-z0-9-]+)?(?:\s*\([^()]+\))?\s*',alternative)
            assert match
            alternatives.append(match[1])
        groups.append(alternatives)
    icu=[group for group in groups if any(name.startswith('libicu') for name in group)]
    if sys.argv[2]=='ubuntu-24.04': assert icu==[['libicu74']]
    elif sys.argv[2]=='debian-13': assert len(icu)==1 and len(icu[0])==2 and set(icu[0])=={'libicu76','libicu72'}
    else: raise ValueError('Unsupported package target')
except (AssertionError,ValueError,subprocess.CalledProcessError):
    raise SystemExit('ANIMAIL: this package has a different distribution dependency policy.')
PY
    printf 'Verified ANIMAIL %s: signature, checksum, package identity and distribution dependencies.\n' "$version"
    [ "$mode" = install ] || { printf '%s\n' 'Verification complete. No administrator command was run.'; return; }
    chmod 755 "$ANIMAIL_WORK"
    chmod 644 "$package"
    printf '%s\n' 'apt will refresh its package list and ask before installing ANIMAIL and its dependencies.'
    /usr/bin/sudo /usr/bin/apt update < /dev/tty
    if pgrep -x ANIMAIL.Native >/dev/null 2>&1; then
        fail 'ANIMAIL was opened while the package was downloading. Choose Exit ANIMAIL from its tray menu, then run the installer again.'
    fi
    /usr/bin/sudo /usr/bin/apt install "$package" < /dev/tty
    printf '\n%s\n' 'ANIMAIL is installed. Open it from your applications menu as your normal user.' 'Unlock your desktop login keyring if requested. Help: https://animail.app/linux/'
}

main "$@"
