#!/usr/bin/env bash
# Install Radiolink M2R on a supported Linux host.
#
# Usage:
#   ./install-m2r.sh [auto|docker|podman]
#   ./install-m2r.sh reset-setup
#   ./install-m2r.sh prepare-image
#   M2R_DOWNLOAD_URL=https://example.org/m2r.tar.gz ./install-m2r.sh
#   ./install-m2r.sh https://example.org/m2r.tar.gz
#
# Run as the normal user.  The explicit "docker" mode can install Docker on a
# fresh appliance; M2R itself is always run by the normal user.
set -Eeuo pipefail

readonly DOCKER_DOCS="https://docs.docker.com/engine/install/"
readonly PODMAN_DOCS="https://podman.io/docs/installation"
readonly DEFAULT_DOWNLOAD_URL="https://ddrnet.dk/download/m2r/m2r.tar.gz"
readonly DEFAULT_DIR="${HOME}/m2r"
readonly DEFAULT_CONFIG_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/m2r"
readonly DEFAULT_DATA_DIR="${XDG_DATA_HOME:-${HOME}/.local/share}/m2r"
readonly DEFAULT_STATE_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/m2r"

die() { printf '\nFejl: %s\n' "$*" >&2; exit 1; }
note() { printf '%s\n' "$*"; }

if [[ ${EUID} -eq 0 ]]; then
    die "Kør scriptet som din almindelige bruger, ikke som root. Se installationsvejledningen for Docker: ${DOCKER_DOCS}"
fi

action="${1:-auto}"
case "$action" in
    auto|docker|podman|reset-setup|prepare-image) shift || true ;;
    https://*) action="auto" ;;
    *) die "Brug: $0 [auto|docker|podman|reset-setup|prepare-image] [https://.../m2r.tar.gz]" ;;
esac
archive_url="${M2R_DOWNLOAD_URL:-${1:-$DEFAULT_DOWNLOAD_URL}}"
install_dir="${M2R_INSTALL_DIR:-$DEFAULT_DIR}"
config_dir="${M2R_CONFIG_DIR:-$DEFAULT_CONFIG_DIR}"
data_dir="${M2R_DATA_DIR:-$DEFAULT_DATA_DIR}"
state_dir="${M2R_STATE_DIR:-$DEFAULT_STATE_DIR}"
expected_sha256="${M2R_SHA256:-}"

if [[ "${M2R_ALLOW_HTTP:-0}" == "1" ]]; then
    [[ "$archive_url" == https://* || "$archive_url" == http://* ]] || die "Downloadadressen skal bruge HTTP eller HTTPS."
    curl_protocols="=http,https"
else
    [[ "$archive_url" == https://* ]] || die "Downloadadressen skal bruge HTTPS. Brug kun M2R_ALLOW_HTTP=1 på et betroet test-LAN."
    curl_protocols="=https"
fi

select_container_runtime() {
    local requested="${M2R_RUNTIME:-$action}"
    COMPOSE_CMD=()

    if [[ "$requested" == "auto" || "$requested" == "docker" ]]; then
        if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
            RUNTIME="Docker"
            COMPOSE_CMD=(docker compose)
            return
        fi
        [[ "$requested" == "docker" ]] && die "Docker Compose kan ikke bruges af denne bruger. Installér/start Docker eller brug M2R_RUNTIME=podman. Se: ${DOCKER_DOCS}"
    fi

    if [[ "$requested" == "auto" || "$requested" == "podman" ]]; then
        if command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then
            if podman compose version >/dev/null 2>&1; then
                RUNTIME="Podman (podman compose)"
                COMPOSE_CMD=(podman compose)
                return
            elif command -v podman-compose >/dev/null 2>&1; then
                RUNTIME="Podman (podman-compose)"
                COMPOSE_CMD=(podman-compose)
                return
            fi
        fi
        [[ "$requested" == "podman" ]] && die "Podman eller en compose-provider mangler/kan ikke bruges. Installér Podman samt 'podman compose' eller 'podman-compose'. Se: ${PODMAN_DOCS}"
    fi

    die "Ingen brugbar container-motor fundet. Installér enten Docker Engine med Compose-plugin (${DOCKER_DOCS}) eller Podman med compose-provider (${PODMAN_DOCS}). Scriptet installerer ikke systemsoftware."
}

install_docker_if_requested() {
    [[ "$action" == "docker" ]] || return
    command -v sudo >/dev/null 2>&1 || die "sudo mangler; Docker kan ikke installeres automatisk."
    command -v curl >/dev/null 2>&1 || die "curl mangler; installér curl og prøv igen."
    local installer current_user
    current_user="$(id -un)"
    if command -v docker >/dev/null 2>&1; then
        docker compose version >/dev/null 2>&1 || die "Docker findes, men Compose-plugin mangler. Installér docker-compose-plugin."
        docker info >/dev/null 2>&1 && return
        sudo usermod -aG docker "$current_user"
        note "$current_user er føjet til docker-gruppen. Log ud og ind igen, og kør: $0 docker"
        exit 0
    fi
    installer="$(mktemp)"
    curl --fail --location --proto '=https' --tlsv1.2 --output "$installer" https://get.docker.com/
    note "Installerer Docker fra det officielle get.docker.com-script."
    sudo sh "$installer"
    sudo usermod -aG docker "$current_user"
    rm -f "$installer"
    if ! docker info >/dev/null 2>&1; then
        note "Docker er installeret, og $current_user er føjet til docker-gruppen."
        note "Log ud og ind igen, og kør derefter: $0 docker"
        exit 0
    fi
}

check_prerequisites() {
    install_docker_if_requested
    select_container_runtime
    command -v curl >/dev/null 2>&1 || die "curl mangler. Installér curl, og prøv igen."
    command -v tar >/dev/null 2>&1 || die "tar mangler. Installér tar, og prøv igen."
    command -v sha256sum >/dev/null 2>&1 || die "sha256sum mangler. Installér coreutils, og prøv igen."
    command -v python3 >/dev/null 2>&1 || die "python3 mangler. Det bruges til lokal konfiguration."
}

choose_compose() {
    local has_gpio=0 has_serial=0 gpio_chip="" serial_device=""
    gpio_chip="$(compgen -G '/dev/gpiochip*' | sort -V | head -n 1 || true)"
    [[ -n "$gpio_chip" ]] && has_gpio=1
    if compgen -G '/dev/ttyUSB*' >/dev/null || compgen -G '/dev/ttyACM*' >/dev/null; then has_serial=1; fi
    serial_device="$( { compgen -G '/dev/ttyUSB*'; compgen -G '/dev/ttyACM*'; } | sort -V | head -n 1 || true)"

    note ""
    note "Fundet hardware: GPIO=${has_gpio}, USB/seriel=${has_serial}"
    if (( has_gpio && has_serial )); then COMPOSE_FILE="docker-compose.pi.yml"
    elif (( has_gpio )); then COMPOSE_FILE="docker-compose.gpio.yml"
    elif (( has_serial )); then COMPOSE_FILE="docker-compose.yml"
    else COMPOSE_FILE="docker-compose.none.yml"; fi
    {
        printf 'M2R_CONFIG_DIR=%s\n' "$config_dir"
        printf 'M2R_DATA_DIR=%s\n' "$data_dir"
        printf 'M2R_STATE_DIR=%s\n' "$state_dir"
        [[ -n "$gpio_chip" ]] && printf 'M2R_GPIO_CHIP=%s\n' "$gpio_chip"
        [[ -n "$serial_device" ]] && printf 'M2R_SERIAL_DEVICE=%s\n' "$serial_device"
        true
    } > "$project_dir/.env"
}

initialize_config() {
    local template_dir="$project_dir/config" backend
    mkdir -p "$config_dir" "$data_dir/cert" "$data_dir/sounds" "$state_dir"
    [[ -f "$template_dir/settings.example.yaml" ]] || die "Arkivet mangler config/settings.example.yaml"
    [[ -f "$template_dir/admin.example.yaml" ]] || die "Arkivet mangler config/admin.example.yaml"
    [[ -f "$template_dir/sound.example.yaml" ]] || die "Arkivet mangler config/sound.example.yaml"

    cp "$template_dir/settings.example.yaml" "$config_dir/settings.yaml"
    cp "$template_dir/sound.example.yaml" "$config_dir/sound.yaml"
    cp "$template_dir/admin.example.yaml" "$config_dir/admin.yaml"
    chmod 600 "$config_dir/admin.yaml"
    backend="none"
    python3 - "$config_dir/settings.yaml" "$backend" <<'PY'
from pathlib import Path
import sys

path = Path(sys.argv[1])
backend = sys.argv[2]
text = path.read_text(encoding="utf-8")
text = text.replace("  backend: serial", f"  backend: {backend}")
if backend == "none":
    text = text.replace("  enabled: true", "  enabled: false", 1)
path.write_text(text, encoding="utf-8")
PY
}

reset_setup() {
    local template="$install_dir/config/admin.example.yaml"
    [[ -f "$template" && -f "$config_dir/settings.yaml" ]] || die "Ingen M2R-installation eller konfiguration fundet"
    cp "$template" "$config_dir/admin.yaml"
    chmod 600 "$config_dir/admin.yaml"
    python3 - "$config_dir/settings.yaml" <<'PY'
from pathlib import Path
import re, sys
path = Path(sys.argv[1])
text = path.read_text(encoding="utf-8")
text = re.sub(r"(?m)(^relay:\n(?:^[ \t]+[^\n]*\n)*?^[ \t]+backend:\s*)\S+", r"\1none", text)
text = re.sub(r"(?m)(^ptt:\n(?:^[ \t]+[^\n]*\n)*?^[ \t]+enabled:\s*)\S+", r"\1false", text)
path.write_text(text, encoding="utf-8")
PY
    note "Førstegangsopsætningen er nulstillet. Åbn http://<maskinens-IP>:8080/setup"
}

prepare_appliance_image() {
    local compose_file="$install_dir/docker-compose.appliance.yml"
    local image="radiolink/m2r-gpio:latest"
    local current_user current_group project_service key_service confirmation path architecture model

    architecture="$(uname -m)"
    model=""
    if [[ -r /proc/device-tree/model ]]; then
        model="$(tr -d '\0' </proc/device-tree/model)"
    fi
    [[ "$architecture" == "aarch64" || "$architecture" == "arm64" ]] || \
        die "prepare-image er kun til 64-bit Raspberry Pi, ikke $architecture."
    [[ "$model" == *"Raspberry Pi"* ]] || \
        die "prepare-image er kun til Raspberry Pi SD-images. Fundet model: ${model:-ukendt}"

    command -v sudo >/dev/null 2>&1 || die "sudo mangler; SD-imaget kan ikke klargøres."
    command -v docker >/dev/null 2>&1 || die "Docker mangler."
    docker compose version >/dev/null 2>&1 || die "Docker Compose-plugin mangler."
    docker info >/dev/null 2>&1 || die "Docker kan ikke bruges af denne bruger."
    docker image inspect "$image" >/dev/null 2>&1 || \
        die "Det aktuelle runtime-image mangler: $image"
    [[ -f "$compose_file" ]] || die "Runtime compose-filen mangler: $compose_file"
    [[ -f "$install_dir/.env" ]] || die "Installationen mangler $install_dir/.env"
    [[ -f "$install_dir/config/settings.example.yaml" ]] || die "Indstillingsskabelonen mangler."
    [[ -f "$install_dir/config/admin.example.yaml" ]] || die "Adminskabelonen mangler."
    [[ -f "$install_dir/config/sound.example.yaml" ]] || die "Lydskabelonen mangler."

    note ""
    note "ADVARSEL: prepare-image sletter M2R-konfiguration, certifikat, lydarkiv,"
    note "hændelser, shell-historik, SSH-hostnøgler og maskinidentitet."
    note "Docker-imaget '$image' bevares og bygges ikke igen."
    read -r -p "Skriv KLARGØR-IMAGE for at fortsætte: " confirmation
    [[ "$confirmation" == "KLARGØR-IMAGE" ]] || die "Klargøring afbrudt."

    current_user="$(id -un)"
    current_group="$(id -gn)"
    [[ "$install_dir" == "$HOME/m2r" ]] || \
        note "Bemærk: brugerdefineret installationsmappe anvendes: $install_dir"

    # These are the only trees whose ownership prepare-image may change.
    # Refuse broad or external targets before using recursive chown.
    for path in "$config_dir" "$data_dir" "$state_dir"; do
        [[ -n "$path" && "$path" == "$HOME/"* && "$path" != "$HOME" ]] || \
            die "Usikker M2R-datasti afvist: $path"
    done

    # Stop safely while replacing configuration.  The permanent systemd unit
    # recreates the containers from the tagged image on every subsequent boot.
    (cd "$install_dir" && docker compose -f "$compose_file" down --remove-orphans)

    sudo install -d -o "$current_user" -g "$current_group" -m 0700 \
        "$config_dir" "$data_dir" "$data_dir/cert" "$state_dir"
    sudo install -d -o "$current_user" -g "$current_group" -m 0755 \
        "$data_dir/sounds"
    sudo chown -R "$current_user:$current_group" "$config_dir" "$data_dir" "$state_dir"
    cp "$install_dir/config/settings.example.yaml" "$config_dir/settings.yaml"
    cp "$install_dir/config/admin.example.yaml" "$config_dir/admin.yaml"
    cp "$install_dir/config/sound.example.yaml" "$config_dir/sound.yaml"
    chmod 600 "$config_dir/admin.yaml"
    python3 - "$config_dir/settings.yaml" <<'PY'
from pathlib import Path
import re, sys
path = Path(sys.argv[1])
text = path.read_text(encoding="utf-8")
text = re.sub(r"(?m)(^relay:\n(?:^[ \t]+[^\n]*\n)*?^[ \t]+backend:\s*)\S+", r"\1none", text)
text = re.sub(r"(?m)(^ptt:\n(?:^[ \t]+[^\n]*\n)*?^[ \t]+enabled:\s*)\S+", r"\1false", text)
path.write_text(text, encoding="utf-8")
PY
    find "$data_dir/cert" -mindepth 1 -maxdepth 1 -type f -delete
    find "$data_dir/sounds" -mindepth 1 -maxdepth 1 -type f -delete
    find "$state_dir" -mindepth 1 -maxdepth 1 -type f -delete

    project_service="$(mktemp)"
    key_service="$(mktemp)"
    cat > "$project_service" <<EOF
[Unit]
Description=Radiolink M2R appliance
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=$current_user
WorkingDirectory=$install_dir
ExecStart=/usr/bin/docker compose -f $compose_file up -d --no-build
ExecStop=/usr/bin/docker compose -f $compose_file stop
RemainAfterExit=yes
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
EOF
    cat > "$key_service" <<'EOF'
[Unit]
Description=Ensure unique SSH host keys
Before=ssh.service sshd.service
After=local-fs.target

[Service]
Type=oneshot
ExecStart=/usr/bin/ssh-keygen -A

[Install]
WantedBy=multi-user.target
EOF
    sudo install -m 0644 "$project_service" /etc/systemd/system/m2r-appliance.service
    sudo install -m 0644 "$key_service" /etc/systemd/system/m2r-ssh-host-keys.service
    rm -f "$project_service" "$key_service"
    sudo systemctl daemon-reload
    sudo systemctl enable m2r-appliance.service m2r-ssh-host-keys.service ssh.service

    # Create the stopped/started container definitions now without building.
    # The service explicitly runs `up` on every boot, even if a prior image was
    # captured after `docker compose down`.
    (cd "$install_dir" && docker compose -f "$compose_file" up -d --no-build)
    docker compose -f "$compose_file" ps

    docker builder prune -f >/dev/null 2>&1 || true
    docker image prune -f >/dev/null 2>&1 || true
    sudo apt-get clean
    sudo journalctl --rotate >/dev/null 2>&1 || true
    sudo journalctl --vacuum-time=1s >/dev/null 2>&1 || true
    rm -f "$HOME/.bash_history" "$HOME/.python_history"
    sudo rm -f /root/.bash_history

    # `uninitialized` (not an empty file) makes ConditionFirstBoot=yes.  The
    # key service is an additional guard and creates missing keys on every boot.
    sudo rm -f /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub
    printf 'uninitialized\n' | sudo tee /etc/machine-id >/dev/null
    sudo rm -f /var/lib/dbus/machine-id

    note ""
    note "SD-kortet er klargjort. Maskinen slukker nu."
    note "Næste boot starter SSH og M2R fra '$image' uden build."
    sudo systemctl poweroff
}

if [[ "$action" == "reset-setup" ]]; then
    command -v python3 >/dev/null 2>&1 || die "python3 mangler."
    reset_setup
    exit 0
fi

if [[ "$action" == "prepare-image" ]]; then
    command -v python3 >/dev/null 2>&1 || die "python3 mangler."
    prepare_appliance_image
    exit 0
fi

check_prerequisites
note "${RUNTIME} er klar. Først nu hentes Radiolink M2R."

tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
archive="$tmp_dir/m2r.tar.gz"
curl --fail --location --proto "$curl_protocols" --tlsv1.2 --output "$archive" "$archive_url"

if [[ -n "$expected_sha256" ]]; then
    printf '%s  %s\n' "$expected_sha256" "$archive" | sha256sum --check --status || die "SHA-256-kontrol fejlede. Installationen er afbrudt."
else
    note "Bemærk: ingen SHA-256 er angivet. Udgiv gerne M2R_SHA256 sammen med downloadadressen."
fi

tar -tzf "$archive" >/dev/null || die "Downloadet er ikke et gyldigt .tar.gz-arkiv."
if [[ -e "$install_dir" && -n "$(find "$install_dir" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]]; then
    die "Installationsmappen er ikke tom: $install_dir"
fi
mkdir -p "$install_dir"
tar -xzf "$archive" --no-same-owner -C "$install_dir"

# Releases may contain one top-level directory or files directly at the root.
project_dir="$install_dir"
if [[ ! -f "$project_dir/docker-compose.yml" ]]; then
    candidate="$(find "$install_dir" -mindepth 1 -maxdepth 2 -type f -name docker-compose.yml -print -quit)"
    [[ -n "$candidate" ]] || die "Arkivet indeholder ikke docker-compose.yml."
    project_dir="$(dirname "$candidate")"
fi

choose_compose
[[ -f "$project_dir/$COMPOSE_FILE" ]] || die "Arkivet mangler $COMPOSE_FILE"
initialize_config

note ""
note "Valgt installation: $COMPOSE_FILE"
note "Projektmappe: $project_dir"
note ""
note "Adgangskoder, Mumble og PTT vælges på http://<maskinens-IP>:8080/setup"
read -r -p "Start containere nu? [Y/n]: " start_now
start_now="${start_now:-y}"
if [[ "$start_now" =~ ^[Yy]$ ]]; then
    (cd "$project_dir" && "${COMPOSE_CMD[@]}" -f "$COMPOSE_FILE" up -d)
    note "Færdig. Åbn derefter http://<maskinens-IP>:8080/setup"
else
    note "Klar. Start senere med: cd \"$project_dir\" && ${COMPOSE_CMD[*]} -f $COMPOSE_FILE up -d"
fi
