#!/usr/bin/env bash
set -euo pipefail

APP_NAME="ScyllaChat"
DEFAULT_MANIFEST_URL="https://app.scylla.love/updates/latest.json"
MANIFEST_URL="${SCYLLA_UPDATE_MANIFEST_URL:-$DEFAULT_MANIFEST_URL}"

OS_NAME="$(uname -s 2>/dev/null || echo unknown)"
case "$OS_NAME" in
    Darwin*)
        DEFAULT_INSTALL_DIR="$HOME/Applications/ScyllaChat"
        ;;
    *)
        DEFAULT_INSTALL_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/ScyllaChat"
        ;;
esac

INSTALL_DIR="${SCYLLA_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
BIN_DIR="${SCYLLA_BIN_DIR:-$HOME/.local/bin}"
NO_LAUNCH="${SCYLLA_INSTALL_NO_LAUNCH:-0}"
NO_SHORTCUT="${SCYLLA_INSTALL_NO_SHORTCUT:-0}"

usage() {
    cat <<EOF
ScyllaChat desktop installer

Usage:
  ./install.sh [--manifest URL] [--dir PATH] [--no-launch] [--no-shortcut]

Environment overrides:
  SCYLLA_UPDATE_MANIFEST_URL   Update manifest URL
  SCYLLA_INSTALL_DIR           Install directory
  SCYLLA_BIN_DIR               Command wrapper directory
  SCYLLA_INSTALL_NO_LAUNCH=1   Do not prompt to launch after install
  SCYLLA_INSTALL_NO_SHORTCUT=1 Do not create desktop/menu launchers
EOF
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        --manifest)
            MANIFEST_URL="${2:?Missing URL after --manifest}"
            shift 2
            ;;
        --manifest=*)
            MANIFEST_URL="${1#--manifest=}"
            shift
            ;;
        --dir)
            INSTALL_DIR="${2:?Missing path after --dir}"
            shift 2
            ;;
        --dir=*)
            INSTALL_DIR="${1#--dir=}"
            shift
            ;;
        --no-launch)
            NO_LAUNCH=1
            shift
            ;;
        --no-shortcut)
            NO_SHORTCUT=1
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            echo "Unknown option: $1" >&2
            usage >&2
            exit 2
            ;;
    esac
done

find_python() {
    for cmd in python3 python; do
        if command -v "$cmd" >/dev/null 2>&1 && "$cmd" -c 'import sys; raise SystemExit(0 if sys.version_info[0] == 3 else 1)' >/dev/null 2>&1; then
            printf '%s\n' "$cmd"
            return 0
        fi
    done
    return 1
}

PYTHON_CMD="$(find_python || true)"
if [ -z "$PYTHON_CMD" ]; then
    echo "ERROR: Python 3 is required to install and run $APP_NAME." >&2
    echo "Install Python 3, then run this installer again." >&2
    exit 1
fi

echo "====================================="
echo "     $APP_NAME Desktop Installer"
echo "====================================="
echo "Manifest: $MANIFEST_URL"
echo "Install:  $INSTALL_DIR"
echo "Python:   $PYTHON_CMD"
echo ""

export SCYLLA_INSTALLER_MANIFEST_URL="$MANIFEST_URL"
export SCYLLA_INSTALLER_INSTALL_DIR="$INSTALL_DIR"

"$PYTHON_CMD" - <<'PY'
import hashlib
import json
import os
import shutil
import stat
import sys
import tempfile
import urllib.request
import zipfile
from pathlib import Path
from urllib.parse import urljoin


manifest_url = os.environ["SCYLLA_INSTALLER_MANIFEST_URL"]
install_dir = Path(os.environ["SCYLLA_INSTALLER_INSTALL_DIR"]).expanduser().resolve()


def fail(message):
    print(f"ERROR: {message}", file=sys.stderr)
    raise SystemExit(1)


def open_url(url):
    request = urllib.request.Request(url, headers={"User-Agent": "ScyllaChat-Installer/1.0"})
    return urllib.request.urlopen(request, timeout=120)


def read_json_url(url):
    with open_url(url) as response:
        return json.loads(response.read().decode("utf-8"))


def choose_web_artifact(manifest):
    artifacts = manifest.get("artifacts") if isinstance(manifest.get("artifacts"), dict) else {}
    artifact = artifacts.get("web-bundle")
    if isinstance(artifact, dict):
        return artifact
    for key, value in artifacts.items():
        if not isinstance(value, dict):
            continue
        filename = str(value.get("filename") or value.get("url") or "")
        if "web" in key.lower() or filename.lower().endswith(".zip"):
            return value
    return None


def download(url, destination):
    digest = hashlib.sha256()
    downloaded = 0
    with open_url(url) as response, open(destination, "wb") as out:
        while True:
            chunk = response.read(1024 * 1024)
            if not chunk:
                break
            out.write(chunk)
            digest.update(chunk)
            downloaded += len(chunk)
            if downloaded == len(chunk) or downloaded % (8 * 1024 * 1024) < len(chunk):
                print(f"Downloaded {downloaded / 1024 / 1024:.1f} MB")
    return digest.hexdigest()


def find_bundle_root(extract_dir):
    root = Path(extract_dir)
    if (root / "scyllachat.html").exists():
        return root
    matches = sorted(root.rglob("scyllachat.html"))
    if not matches:
        fail("Downloaded desktop bundle does not contain scyllachat.html")
    return matches[0].parent


def remove_path(path):
    if path.is_dir() and not path.is_symlink():
        shutil.rmtree(path)
    else:
        path.unlink(missing_ok=True)


def copy_item(src, dst):
    if dst.exists() and src.is_dir() != dst.is_dir():
        remove_path(dst)
    if src.is_dir():
        shutil.copytree(src, dst, dirs_exist_ok=True)
    else:
        dst.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(src, dst)


manifest = read_json_url(manifest_url)
artifact = choose_web_artifact(manifest)
if not artifact:
    fail("No web-bundle artifact found in update manifest")

artifact_url = artifact.get("url")
if not artifact_url:
    fail("web-bundle artifact is missing a URL")
artifact_url = urljoin(manifest_url, str(artifact_url))

expected_hash = str(artifact.get("sha256") or "").strip().lower()
version = str(manifest.get("version") or "unknown")
version_code = manifest.get("version_code", 0)

with tempfile.TemporaryDirectory(prefix="scyllachat_install_") as tmp:
    tmp_path = Path(tmp)
    zip_path = tmp_path / "ScyllaChat.zip"
    extract_dir = tmp_path / "extract"

    print(f"Downloading {artifact_url}")
    actual_hash = download(artifact_url, zip_path)
    if expected_hash and actual_hash.lower() != expected_hash:
        fail(f"SHA-256 mismatch: expected {expected_hash}, got {actual_hash}")

    print("Extracting desktop bundle")
    extract_dir.mkdir()
    with zipfile.ZipFile(zip_path, "r") as archive:
        archive.extractall(extract_dir)

    bundle_root = find_bundle_root(extract_dir)
    install_dir.mkdir(parents=True, exist_ok=True)

    skip_names = {"data", ".playwright-data", "__pycache__"}
    print(f"Installing to {install_dir}")
    for item in bundle_root.iterdir():
        if item.name in skip_names:
            continue
        copy_item(item, install_dir / item.name)

    deps_marker = install_dir / ".deps_installed"
    deps_marker.unlink(missing_ok=True)

    for script_name in [
        "launch.sh",
        "launch-with-cloudflared.sh",
        "start_servers.sh",
        "download-cloudflared.sh",
        "install.sh",
    ]:
        script = install_dir / script_name
        if script.exists():
            script.chmod(script.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

    scripts_dir = install_dir / "scripts"
    for script_name in [
        "launch.sh",
        "launch-with-cloudflared.sh",
        "start_servers.sh",
        "detect_python.sh",
        "download-cloudflared.sh",
        "install.sh",
    ]:
        script = scripts_dir / script_name
        if script.exists():
            script.chmod(script.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

    installed_version = {
        "app": manifest.get("app", "ScyllaChat"),
        "version": version,
        "version_code": version_code,
        "manifest_url": manifest_url,
        "artifact_url": artifact_url,
        "sha256": actual_hash,
    }
    with open(install_dir / "installed_version.json", "w", encoding="utf-8") as handle:
        json.dump(installed_version, handle, indent=2)
        handle.write("\n")

print(f"Installed ScyllaChat {version} ({version_code})")
PY

if [ "$NO_SHORTCUT" != "1" ]; then
    mkdir -p "$BIN_DIR"
    WRAPPER="$BIN_DIR/scyllachat"
    {
        printf '%s\n' '#!/usr/bin/env bash'
        printf 'cd %q\n' "$INSTALL_DIR"
        printf '%s\n' 'exec ./launch.sh "$@"'
    } > "$WRAPPER"
    chmod +x "$WRAPPER"
    echo "Command wrapper: $WRAPPER"

    if [ "$OS_NAME" = "Linux" ]; then
        APPLICATIONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
        DESKTOP_FILE="$APPLICATIONS_DIR/scyllachat.desktop"
        DESKTOP_SHORTCUT=""
        if command -v xdg-user-dir >/dev/null 2>&1; then
            DESKTOP_SHORTCUT="$(xdg-user-dir DESKTOP 2>/dev/null || true)"
        fi
        if [ -z "$DESKTOP_SHORTCUT" ]; then
            DESKTOP_SHORTCUT="$HOME/Desktop"
        fi
        mkdir -p "$APPLICATIONS_DIR"
        cat > "$DESKTOP_FILE" <<EOF
[Desktop Entry]
Type=Application
Name=ScyllaChat
Comment=Private AI chat web UI
Exec=$WRAPPER
Icon=$INSTALL_DIR/logo_notext.png
Terminal=true
StartupNotify=false
Categories=Utility;
EOF
        chmod +x "$DESKTOP_FILE"
        if command -v update-desktop-database >/dev/null 2>&1; then
            update-desktop-database "$APPLICATIONS_DIR" >/dev/null 2>&1 || true
        fi
        echo "Desktop entry: $DESKTOP_FILE"
        if [ -d "$DESKTOP_SHORTCUT" ]; then
            DESKTOP_LAUNCHER="$DESKTOP_SHORTCUT/ScyllaChat.desktop"
            cp "$DESKTOP_FILE" "$DESKTOP_LAUNCHER"
            chmod +x "$DESKTOP_LAUNCHER"
            if command -v gio >/dev/null 2>&1; then
                gio set "$DESKTOP_LAUNCHER" metadata::trusted true >/dev/null 2>&1 || true
            fi
            echo "Desktop shortcut: $DESKTOP_LAUNCHER"
        fi
    elif [ "$OS_NAME" = "Darwin" ] && [ -d "$HOME/Desktop" ]; then
        COMMAND_FILE="$HOME/Desktop/ScyllaChat.command"
        {
            printf '%s\n' '#!/usr/bin/env bash'
            printf 'cd %q\n' "$INSTALL_DIR"
            printf '%s\n' 'exec ./launch.sh'
        } > "$COMMAND_FILE"
        chmod +x "$COMMAND_FILE"
        echo "Desktop launcher: $COMMAND_FILE"
    fi
fi

echo ""
echo "Install complete."
if [ "$NO_SHORTCUT" != "1" ]; then
    echo "Launch later:"
    if [ "$OS_NAME" = "Linux" ]; then
        echo "  App menu/Desktop: ScyllaChat"
    elif [ "$OS_NAME" = "Darwin" ]; then
        echo "  Desktop: ScyllaChat.command"
    fi
    if [ -x "$WRAPPER" ]; then
        case ":$PATH:" in
            *":$BIN_DIR:"*)
                echo "  Terminal: scyllachat"
                ;;
            *)
                echo "  Terminal: $WRAPPER"
                echo "  Note: $BIN_DIR is not currently on PATH in this shell."
                ;;
        esac
    fi
else
    echo "Launch later:"
fi
echo "  Direct: cd \"$INSTALL_DIR\" && ./launch.sh"
echo "Installed files: $INSTALL_DIR"

if [ "$NO_LAUNCH" != "1" ] && [ -t 0 ]; then
    printf "Launch ScyllaChat now? [Y/n] "
    read -r answer
    case "${answer:-Y}" in
        n|N|no|NO|No)
            ;;
        *)
            cd "$INSTALL_DIR"
            exec ./launch.sh
            ;;
    esac
fi
