#!/bin/sh
# MFTPlus install script
#
# Every install is verified by default (COO-815):
#   - SHA256 of the downloaded archive is always checked against the release's
#     published SHA256SUMS manifest — a mismatch aborts the install;
#   - when minisign, a signature, and the MFTPlus release public key are
#     available, the detached signature proving release ORIGIN is validated too
#     (signature unavailable degrades to a loud warning; a BAD signature never
#     continues).
# Usage:
#   curl -fsSL https://releases.mftplus.co.za/install.sh | sh
#   curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --version v0.7.0
#   curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --verify
#   curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --dry-run
#   curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --prefix ~/.local
#   curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --uninstall
#   curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --binary mft-agent-cli
#
# Environment:
#   RELEASES_BASE         Release mirror base URL (default: official releases host)
#   MFTPLUS_PUBKEY_FILE   Path to the minisign release public key on disk
#                         (overrides MFTPLUS_PUBKEY_URL)
#   MFTPLUS_PUBKEY_URL    Where to fetch the release public key from
#                         (default: ${RELEASES_BASE}/mftplus-release.pub)
#   MINISIGN_BIN          Path to the minisign binary (default: `minisign` on PATH)

set -e
unset CDPATH

# ---- constants ----
RELEASES_BASE="${RELEASES_BASE:-https://releases.mftplus.co.za}"
DEFAULT_VERSION="latest"
PUBKEY_URL="${MFTPLUS_PUBKEY_URL:-${RELEASES_BASE}/mftplus-release.pub}"
PUBKEY_FILE="${MFTPLUS_PUBKEY_FILE:-}"
MINISIGN="${MINISIGN_BIN:-minisign}"

# ---- output helpers ----
info()  { printf '\033[32m[INFO]\033[0m %s\n' "$*"; }
ok()    { printf '\033[36m[ OK ]\033[0m %s\n' "$*"; }
warn()  { printf '\033[33m[WARN]\033[0m %s\n' "$*"; }
error() { printf '\033[31m[ERROR]\033[0m %b\n' "$*"; exit 1; }

# ---- platform detection ----
detect_platform() {
    OS=$(uname -s)
    ARCH=$(uname -m)

    case "$OS" in
        Linux)  OS="linux" ;;
        Darwin) OS="darwin" ;;
        *)      error "Unsupported OS: $OS (use PowerShell installer or manual download from https://releases.mftplus.co.za for Windows)" ;;
    esac

    case "$ARCH" in
        x86_64|amd64) ARCH="amd64" ;;
        aarch64|arm64) ARCH="aarch64" ;;
        *) error "Unsupported architecture: $ARCH (MFTPlus supports amd64 and aarch64/arm64)" ;;
    esac

    PLATFORM="${OS}_${ARCH}"
    info "Detected: $OS $ARCH"
}

# ---- set binary/archive names (after version is resolved) ----
set_names() {
    VER="${VERSION#v}"  # strip leading v

    # Different binaries have different platform availability
    case "$BINARY" in
        mftctl)
            # mftctl: Linux (amd64, aarch64), macOS (amd64, arm64, universal)
            if [ "$OS" = "darwin" ]; then
                # macOS: prefer universal, fall back to arch-specific
                BINARY_NAME="${BINARY}_${VER}_macos-universal"
                ARCHIVE_NAME="${BINARY_NAME}.tar.gz"
            else
                BINARY_NAME="${BINARY}_${VER}_${OS}_${ARCH}"
                ARCHIVE_NAME="${BINARY_NAME}.tar.gz"
            fi
            ;;
        mft-agent-cli)
            # mft-agent-cli: Linux (amd64, aarch64), macOS (universal only)
            if [ "$OS" = "darwin" ]; then
                BINARY_NAME="${BINARY}_${VER}_macos-universal"
            else
                BINARY_NAME="${BINARY}_${VER}_${OS}_${ARCH}"
            fi
            ARCHIVE_NAME="${BINARY_NAME}.tar.gz"
            ;;
        mft-discover)
            # mft-discover: Linux amd64 only
            if [ "$OS" = "darwin" ]; then
                error "mft-discover is not available for macOS (Linux and Windows only)"
            fi
            if [ "$ARCH" != "amd64" ]; then
                error "mft-discover is only available for Linux amd64 (not $ARCH)"
            fi
            BINARY_NAME="${BINARY}_${VER}_linux_amd64"
            ARCHIVE_NAME="${BINARY_NAME}.tar.gz"
            ;;
        *)
            error "Unknown binary: $BINARY (valid: mftctl, mft-agent-cli, mft-discover)"
            ;;
    esac
}

# ---- version resolution ----
resolve_version() {
    if [ "$VERSION" = "latest" ]; then
        # List the latest directory to get the version
        VERSION=$(curl -fsSL "${RELEASES_BASE}/" 2>/dev/null | grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+' | sort -V | tail -1 || echo "")
        [ -z "$VERSION" ] && VERSION="v0.7.0"
    fi
    info "Version: $VERSION"
}

# ---- verification ----
SIGN_STATUS="unchecked"   # "verified" once an origin signature validates

sha256_cmd() {
    if command -v sha256sum >/dev/null 2>&1; then
        echo "sha256sum"
    elif command -v shasum >/dev/null 2>&1; then
        echo "shasum -a 256"
    else
        return 1
    fi
}

verify_checksums() {
    # $1 = directory holding ${ARCHIVE_NAME} and SHA256SUMS (COO-812 layout)
    DIR=$1

    EXPECTED=$(awk -v n="$ARCHIVE_NAME" '$2 == "*" n || $2 == n {print $1}' "${DIR}/SHA256SUMS" | head -1)
    if [ -z "$EXPECTED" ]; then
        error "Checksum for ${ARCHIVE_NAME} not found in SHA256SUMS.\nThe release manifest does not describe this artifact — refusing to continue.\nRe-download from ${RELEASES_BASE}/${VERSION}/ or pin a concrete version with --version."
    fi

    CHECK_CMD=$(sha256_cmd) || \
        error "Neither sha256sum nor shasum is available — cannot verify the download.\nInstall GNU coreutils (or macOS shasum) and retry."

    ACTUAL=$($CHECK_CMD "${DIR}/${ARCHIVE_NAME}" | awk '{print $1}')
    if [ "$ACTUAL" != "$EXPECTED" ]; then
        error "Checksum MISMATCH for ${ARCHIVE_NAME} — the download is corrupt OR tampered with.\n  Expected: $EXPECTED\n  Actual:   $ACTUAL\nDo NOT install or run this file. Delete it and re-download from\n${RELEASES_BASE}/${VERSION}/ ; if the mismatch persists,\nreport it to security@mftplus.co.za."
    fi
    ok "SHA256 verified: ${ARCHIVE_NAME}"
}

verify_signature() {
    # $1 = directory holding SHA256SUMS / ${ARCHIVE_NAME}
    # Best-effort ORIGIN check: runs whenever signature material (detached
    # signature + public key + minisign tool) is available. Missing material
    # degrades to a loud warning; a signature that FAILS always aborts.
    DIR=$1

    if [ -n "$PUBKEY_FILE" ] && [ ! -f "$PUBKEY_FILE" ]; then
        warn "Origin signature NOT checked: MFTPLUS_PUBKEY_FILE=${PUBKEY_FILE} does not exist"
        warn "  Fix the path, or unset MFTPLUS_PUBKEY_FILE to fetch the key from ${PUBKEY_URL}"
        return 0
    fi

    if ! command -v "$MINISIGN" >/dev/null 2>&1; then
        warn "Origin signature NOT checked: '$MINISIGN' is not installed (checksums were still verified)"
        warn "  Install minisign for full verification: brew install minisign | apt-get install minisign"
        warn "  Or point MINISIGN_BIN at an existing binary"
        return 0
    fi

    if [ -z "$PUBKEY_FILE" ]; then
        PUBKEY_FILE="${DIR}/mftplus-release.pub"
        if ! curl -fsSL --show-error -o "$PUBKEY_FILE" "$PUBKEY_URL" 2>/dev/null; then
            rm -f "$PUBKEY_FILE"
            PUBKEY_FILE=""
            warn "Origin signature NOT checked: release public key unavailable at ${PUBKEY_URL}"
            warn "  Get the key from https://docs.mftplus.co.za/install/verify and set"
            warn "  MFTPLUS_PUBKEY_FILE=<path> (or MFTPLUS_PUBKEY_URL=<url>) to enable origin checks"
            return 0
        fi
    fi

    # Trust root first: a valid signature over SHA256SUMS covers the archive
    # via its checksum entry; fall back to the archive's own detached sig.
    SIG="${DIR}/SHA256SUMS.minisig"
    TARGET="${DIR}/SHA256SUMS"
    if ! curl -fsSL --show-error -o "$SIG" "${RELEASES_BASE}/${VERSION}/SHA256SUMS.minisig" 2>/dev/null; then
        SIG="${DIR}/${ARCHIVE_NAME}.minisig"
        TARGET="${DIR}/${ARCHIVE_NAME}"
        if ! curl -fsSL --show-error -o "$SIG" "${RELEASES_BASE}/${VERSION}/${ARCHIVE_NAME}.minisig" 2>/dev/null; then
            rm -f "$SIG"
            warn "Origin signature NOT checked: no minisign signature published for ${VERSION}"
            return 0
        fi
    fi

    if ! "$MINISIGN" -V -q -p "$PUBKEY_FILE" -x "$SIG" -m "$TARGET" >/dev/null 2>&1; then
        error "SIGNATURE VERIFICATION FAILED for $(basename "$TARGET").\nThis download did not come from MFTPlus or was modified after signing —\na classic indicator of tampering or a compromised mirror.\nDo NOT install or run this file. Delete every downloaded copy,\nre-download from ${RELEASES_BASE}/${VERSION}/ and retry;\nreport to security@mftplus.co.za if it persists."
    fi

    SIGN_STATUS="verified"
    ok "Minisign signature verified: $(basename "$TARGET")"
}

run_verification() {
    # $1 = directory holding the downloaded release files
    verify_checksums "$1"
    verify_signature "$1"
}

warn_skip_verification() {
    warn "=================================================================="
    warn "VERIFICATION DISABLED (--skip-verify)"
    warn "Integrity AND origin of ${ARCHIVE_NAME} will NOT be checked:"
    warn "a corrupted or tampered download would be installed as-is."
    warn "Only do this on a fully trusted network and at your own risk."
    warn "=================================================================="
}

# ---- download + verify ----
fetch_release() {
    DL_URL="${RELEASES_BASE}/${VERSION}/${ARCHIVE_NAME}"
    SUM_URL="${RELEASES_BASE}/${VERSION}/SHA256SUMS"

    info "Downloading ${BINARY} from ${DL_URL} ..."
    curl -fsSL --show-error -o "${TMPDIR}/${ARCHIVE_NAME}" "$DL_URL" || \
        error "Download failed (${DL_URL})"

    # The signed checksum manifest is mandatory for verification.
    curl -fsSL --show-error -o "${TMPDIR}/SHA256SUMS" "$SUM_URL" || \
        error "Could not download SHA256SUMS (${SUM_URL}).\nEvery MFTPlus release publishes a signed checksum manifest; without it\nthe download cannot be verified. Check your connection/proxy and retry."
}

download_and_verify() {
    TMPDIR=$(mktemp -d) || error "Cannot create temp directory"
    trap 'rm -rf "$TMPDIR"' EXIT INT TERM

    fetch_release

    if [ "$VERIFY_ENABLED" = true ]; then
        run_verification "$TMPDIR"
    else
        warn_skip_verification
    fi
}

extract_binary() {
    # Extract archive (handles both flat and directory-wrapped archives)
    tar xzf "${TMPDIR}/${ARCHIVE_NAME}" -C "$TMPDIR" 2>/dev/null || \
        error "Failed to extract ${ARCHIVE_NAME}"

    # Find the binary (may be in a subdirectory)
    BIN_PATH=$(find "$TMPDIR" -name "${BINARY}" -type f 2>/dev/null | head -1)
    [ -z "$BIN_PATH" ] && error "Binary '${BINARY}' not found in archive"

    mv "$BIN_PATH" "${TMPDIR}/${BINARY}" 2>/dev/null || true
    chmod +x "${TMPDIR}/${BINARY}"
}

# ---- verify only (no install) ----
verify_only() {
    TMPDIR=$(mktemp -d) || error "Cannot create temp directory"
    trap 'rm -rf "$TMPDIR"' EXIT INT TERM

    info "Verify-only mode: checking ${ARCHIVE_NAME} (nothing will be installed)"
    fetch_release
    run_verification "$TMPDIR"

    if [ "$SIGN_STATUS" = "verified" ]; then
        info "Verification PASSED — ${ARCHIVE_NAME}: SHA256 OK, origin signature OK"
    else
        info "Verification PASSED — ${ARCHIVE_NAME}: SHA256 OK (origin signature was NOT checked; see warnings above)"
    fi
    info "Install this version with: curl -fsSL ${RELEASES_BASE}/install.sh | sh -s -- --binary ${BINARY} --version ${VERSION}"
}

# ---- install ----
install_binary() {
    TMPDIR=$1

    mkdir -p "$PREFIX" 2>/dev/null || true
    TARGET="${PREFIX}/${BINARY}"

    if [ -w "$PREFIX" ]; then
        mv "${TMPDIR}/${BINARY}" "$TARGET"
    else
        warn "No write permission to ${PREFIX} — using sudo"
        sudo mv "${TMPDIR}/${BINARY}" "$TARGET"
    fi

    info "Installed to ${TARGET}"

    case ":$PATH:" in
        *":${PREFIX}:"*) ;;
        *) warn "${PREFIX} is not in PATH — add 'export PATH=\"${PREFIX}:\$PATH\"' to your shell profile" ;;
    esac
}

# ---- show version ----
show_version() {
    if command -v "${BINARY}" >/dev/null 2>&1; then
        "${BINARY}" --version 2>/dev/null || \
            info "${BINARY} installed (version unknown)"
    else
        warn "${BINARY} not found in PATH"
    fi
}

# ---- uninstall ----
uninstall() {
    FOUND=0
    for d in /usr/local/bin "$HOME/.local/bin"; do
        if [ -f "${d}/${BINARY}" ]; then
            info "Removing ${d}/${BINARY}"
            rm -f "${d}/${BINARY}" 2>/dev/null || sudo rm -f "${d}/${BINARY}"
            FOUND=1
        fi
    done

    if [ -f "/usr/local/bin/${BINARY}" ]; then
        rm -f "/usr/local/bin/${BINARY}" 2>/dev/null || sudo rm -f "/usr/local/bin/${BINARY}"
        FOUND=1
    fi

    [ $FOUND -eq 0 ] && warn "${BINARY} is not installed"
}

# ---- usage ----
usage() {
    cat <<EOF
MFTPlus Install Script

Install MFTPlus binaries (mftctl, mft-agent-cli, mft-discover) on Linux/macOS.

USAGE:
  curl -fsSL https://releases.mftplus.co.za/install.sh | sh [FLAGS]

FLAGS:
  --binary <name>       Binary to install: mftctl (default), mft-agent-cli, mft-discover
  --version <ver>       Version to install (default: latest)
  --prefix <path>       Install prefix (default: /usr/local/bin, or ~/.local/bin if no write access)
  --verify              Download and verify only — report pass/fail, install nothing
  --skip-verify         Skip verification (loud warning; not recommended)
  --dry-run             Show what would be installed without downloading
  --uninstall           Remove the installed binary
  --help                Show this help

VERIFICATION (always on unless --skip-verify):
  Every download is checked against the release's published SHA256SUMS
  manifest. When minisign and the MFTPlus release public key are available,
  the detached signature proving release origin is verified as well.
  A checksum or signature mismatch aborts with instructions; missing
  signature material only warns. Public key & manual walkthrough:
  https://docs.mftplus.co.za/install/verify

EXAMPLES:
  # Install latest mftctl (default)
  curl -fsSL https://releases.mftplus.co.za/install.sh | sh

  # Install specific version
  curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --version v0.7.0

  # Verify a release without installing it
  curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --verify --version v0.7.0

  # Install mft-agent-cli
  curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --binary mft-agent-cli

  # Install to custom location
  curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --prefix ~/.local

  # Uninstall
  curl -fsSL https://releases.mftplus.co.za/install.sh | sh -s -- --uninstall

PLATFORM SUPPORT:
  mftctl:         Linux (amd64, aarch64), macOS (amd64, arm64, universal)
  mft-agent-cli:  Linux (amd64, aarch64), macOS (universal only)
  mft-discover:   Linux amd64 only

Windows users: Use PowerShell installer or download from https://releases.mftplus.co.za
EOF
}

# ---- main ----
main() {
    # Defaults
    BINARY="mftctl"
    VERSION="$DEFAULT_VERSION"
    PREFIX="/usr/local/bin"
    DRY_RUN=false
    UNINSTALL=false
    VERIFY_ONLY=false
    SKIP_VERIFY=false

    # Parse arguments
    while [ $# -gt 0 ]; do
        case "$1" in
            --binary)
                BINARY="$2"
                shift 2
                ;;
            --version)
                VERSION="$2"
                shift 2
                ;;
            --prefix)
                PREFIX="$2"
                shift 2
                ;;
            --dry-run)
                DRY_RUN=true
                shift
                ;;
            --uninstall)
                UNINSTALL=true
                shift
                ;;
            --verify)
                VERIFY_ONLY=true
                shift
                ;;
            --skip-verify)
                SKIP_VERIFY=true
                shift
                ;;
            --help|-h)
                usage
                exit 0
                ;;
            *)
                error "Unknown option: $1 (try --help)"
                ;;
        esac
    done

    if [ "$VERIFY_ONLY" = true ] && [ "$SKIP_VERIFY" = true ]; then
        error "--verify and --skip-verify are mutually exclusive"
    fi

    if [ "$SKIP_VERIFY" = true ]; then
        VERIFY_ENABLED=false
    else
        VERIFY_ENABLED=true
    fi

    # Validate binary choice early
    case "$BINARY" in
        mftctl|mft-agent-cli|mft-discover) ;;
        *) error "Invalid binary: $BINARY (valid: mftctl, mft-agent-cli, mft-discover)" ;;
    esac

    if [ "$UNINSTALL" = true ]; then
        uninstall
        exit 0
    fi

    detect_platform
    resolve_version
    set_names

    if [ "$DRY_RUN" = true ]; then
        info "Dry run: would install ${BINARY} ${VERSION} for ${PLATFORM}"
        info "Download URL: ${RELEASES_BASE}/${VERSION}/${ARCHIVE_NAME}"
        info "Install target: ${PREFIX}/${BINARY}"
        exit 0
    fi

    if [ "$VERIFY_ONLY" = true ]; then
        verify_only
        exit 0
    fi

    download_and_verify
    extract_binary
    install_binary "$TMPDIR"
    show_version

    info "Done! Try '${BINARY} --help' for usage."
}

main "$@"
