#!/usr/bin/env bash

set -euo pipefail

readonly BASE_URL="${NTN_BASE_URL:-https://ntn.dev}"
readonly REQUESTED_VERSION="${NTN_VERSION:-latest}"

if [[ "${REQUESTED_VERSION}" != "latest" && ! "${REQUESTED_VERSION}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
	printf 'error: invalid version "%s" -- expected "latest" or a semver like "1.2.3" or "v1.2.3"\n' "${REQUESTED_VERSION}" >&2
	exit 1
fi

# Prints an informational message with the installer prefix.
function info() {
	printf '==> %s\n' "$*" >&2
}

# Prints an error message and exits the installer.
function fail() {
	printf 'error: %s\n' "$*" >&2
	exit 1
}

# Requires a command to be available before the installer continues.
function require_command() {
	local command_name="$1"
	command -v "$command_name" >/dev/null 2>&1 || fail "Missing required command: ${command_name}"
}

# Removes trailing slashes so PATH component comparisons remain exact but forgiving.
function normalize_path_component() {
	local path="$1"
	while [[ "${path}" != "/" && "${path}" == */ ]]; do
		path="${path%/}"
	done
	printf '%s\n' "${path}"
}

# Returns true when PATH contains the exact directory component, after slash normalization.
function path_contains_dir() {
	local dir
	dir="$(normalize_path_component "$1")"
	local path_entry
	local IFS=:
	for path_entry in ${PATH:-}; do
		if [[ "$(normalize_path_component "${path_entry}")" == "${dir}" ]]; then
			return 0
		fi
	done
	return 1
}

# Returns true when a path is non-empty and absolute.
function is_absolute_path() {
	local path="$1"
	[[ -n "${path}" && "${path}" == /* ]]
}

# Returns true when an existing absolute directory can receive an executable.
function can_install_to_dir() {
	local dir="$1"
	is_absolute_path "${dir}" || return 1
	[[ -d "${dir}" && -w "${dir}" && -x "${dir}" ]]
}

# Validates and creates the explicit install directory before downloads begin.
function prepare_explicit_install_dir() {
	local dir="$1"
	is_absolute_path "${dir}" || return 1
	if [[ ! -d "${dir}" ]]; then
		mkdir -p "${dir}" 2>/dev/null || return 1
	fi
	[[ -w "${dir}" && -x "${dir}" ]]
}

INSTALL_DIR=""
INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS="false"

# Records the chosen install directory and whether PATH guidance is needed.
function use_install_dir() {
	INSTALL_DIR="$1"
	if ! path_contains_dir "${INSTALL_DIR}"; then
		INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS="true"
	fi
}

# Creates the final user-local fallback directory or fails with override guidance.
function create_home_local_bin_or_fail() {
	if [[ -z "${HOME:-}" ]]; then
		fail "Could not find a writable install directory. Set NTN_INSTALL_DIR to a directory you can write to and re-run the installer."
	fi

	local home_local_bin="${HOME}/.local/bin"
	if ! is_absolute_path "${home_local_bin}"; then
		fail "Could not find a writable install directory. Set NTN_INSTALL_DIR to a directory you can write to and re-run the installer."
	fi

	if [[ -e "${home_local_bin}" ]]; then
		fail "Could not find a writable install directory. Set NTN_INSTALL_DIR to a directory you can write to and re-run the installer."
	fi

	if mkdir -p "${home_local_bin}" 2>/dev/null; then
		use_install_dir "${home_local_bin}"
		INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS="true"
		return
	fi

	fail "Could not create ${home_local_bin}. Set NTN_INSTALL_DIR to a directory you can write to and re-run the installer."
}

# Chooses the install directory in precedence order:
# 1. explicit NTN_INSTALL_DIR
# 2. writable $HOME/.local/bin, $HOME/bin, or /usr/local/bin already on PATH
# 3. existing writable $HOME/.local/bin or $HOME/bin
# 4. newly created $HOME/.local/bin
function choose_install_dir() {
	if [[ -n "${NTN_INSTALL_DIR:-}" ]]; then
		if ! prepare_explicit_install_dir "${NTN_INSTALL_DIR}"; then
			fail "NTN_INSTALL_DIR must be an absolute directory you can create and write to: ${NTN_INSTALL_DIR}"
		fi
		use_install_dir "${NTN_INSTALL_DIR}"
		return
	fi

	if [[ -n "${HOME:-}" ]]; then
		local home_local_bin="${HOME}/.local/bin"
		local home_bin="${HOME}/bin"

		if can_install_to_dir "${home_local_bin}" && path_contains_dir "${home_local_bin}"; then
			use_install_dir "${home_local_bin}"
			return
		fi

		if can_install_to_dir "${home_bin}" && path_contains_dir "${home_bin}"; then
			use_install_dir "${home_bin}"
			return
		fi
	fi

	if can_install_to_dir "/usr/local/bin" && path_contains_dir "/usr/local/bin"; then
		use_install_dir "/usr/local/bin"
		return
	fi

	if [[ -n "${HOME:-}" ]]; then
		local home_local_bin="${HOME}/.local/bin"
		local home_bin="${HOME}/bin"

		if can_install_to_dir "${home_local_bin}"; then
			use_install_dir "${home_local_bin}"
			return
		fi

		if can_install_to_dir "${home_bin}"; then
			use_install_dir "${home_bin}"
			return
		fi
	fi

	create_home_local_bin_or_fail
}

# Selects curl or wget for release downloads.
function detect_downloader() {
	if command -v curl >/dev/null 2>&1; then
		DOWNLOADER="curl"
	elif command -v wget >/dev/null 2>&1; then
		DOWNLOADER="wget"
	else
		fail "Either curl or wget is required but neither is installed"
	fi
}

# Downloads a URL to stdout or to the provided output path.
function download() {
	local url="$1"
	local output="${2:-}"

	if [[ "${DOWNLOADER}" == "curl" ]]; then
		if [[ -n "${output}" ]]; then
			curl -fsSL -o "${output}" "${url}"
		else
			curl -fsSL "${url}"
		fi
	else
		if [[ -n "${output}" ]]; then
			wget -q -O "${output}" "${url}"
		else
			wget -q -O - "${url}"
		fi
	fi
}

# Detects the release target for the current operating system and architecture.
function detect_target() {
	local os
	local arch

	os="$(uname -s)"
	arch="$(uname -m)"

	# Detect Rosetta 2: if running as x86_64 under Rosetta on an ARM Mac,
	# use the native arm64 binary instead.
	if [[ "${os}" == "Darwin" && "${arch}" == "x86_64" ]]; then
		if [[ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" == "1" ]]; then
			arch="arm64"
		fi
	fi

	case "${os}" in
	MINGW* | MSYS* | CYGWIN*)
		fail "This installer does not support Windows. Install via npm: npm install -g ntn"
		;;
	esac

	case "${os}:${arch}" in
	Darwin:arm64 | Darwin:aarch64)
		NTN_TARGET="aarch64-apple-darwin"
		NTN_PLATFORM_LABEL="darwin-arm64"
		;;
	Darwin:x86_64)
		NTN_TARGET="x86_64-apple-darwin"
		NTN_PLATFORM_LABEL="darwin-x64"
		;;
	Linux:x86_64)
		NTN_TARGET="x86_64-unknown-linux-musl"
		NTN_PLATFORM_LABEL="linux-x64"
		;;
	Linux:arm64 | Linux:aarch64)
		NTN_TARGET="aarch64-unknown-linux-musl"
		NTN_PLATFORM_LABEL="linux-arm64"
		;;
	*)
		fail "ntn does not support ${os} ${arch}"
		;;
	esac
}

# Normalizes requested versions so release URLs always include a v prefix.
function normalize_version() {
	local version="$1"
	if [[ "${version}" == v* ]]; then
		printf '%s\n' "${version}"
	else
		printf 'v%s\n' "${version}"
	fi
}

# Resolves the requested version to a concrete release version.
function resolve_version() {
	if [[ "${REQUESTED_VERSION}" == "latest" ]]; then
		download "${BASE_URL}/latest.txt" | tr -d '[:space:]'
	else
		normalize_version "${REQUESTED_VERSION}"
	fi
}

# Verifies the downloaded archive checksum with the available SHA-256 tool.
function verify_archive() {
	local archive_dir="$1"
	local archive_name="$2"
	local checksum_name="$3"

	if command -v shasum >/dev/null 2>&1; then
		(cd "${archive_dir}" && shasum -a 256 -c "${checksum_name}") || fail "Checksum verification failed"
		return
	fi

	if command -v sha256sum >/dev/null 2>&1; then
		(cd "${archive_dir}" && sha256sum -c "${checksum_name}") || fail "Checksum verification failed"
		return
	fi

	fail "No checksum tool found (need shasum or sha256sum)"
}

# Installs the extracted ntn binary into the chosen install directory.
function install_binary() {
	local binary_path="$1"
	local destination_path="${INSTALL_DIR}/ntn"

	if mkdir -p "${INSTALL_DIR}" 2>/dev/null && install -m 0755 "${binary_path}" "${destination_path}" 2>/dev/null; then
		return
	fi

	cat >&2 <<EOF
error: Could not install the Notion CLI to ${destination_path}

The installer could not create or write to ${INSTALL_DIR}. This usually means the
directory is owned by another user or requires administrator permissions.

Choose a directory you can write to and re-run the installer with NTN_INSTALL_DIR:

  curl -fsSL "${BASE_URL}" | NTN_INSTALL_DIR="\$HOME/.local/bin" bash

You can also manually download the binary archive from:

  ${ARCHIVE_URL}
EOF
	exit 1
}

detect_downloader
require_command tar
require_command uname
require_command mktemp
require_command install

detect_target

choose_install_dir
readonly INSTALL_DIR
readonly INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS

VERSION="$(resolve_version)" || true
readonly VERSION
[[ -n "${VERSION}" ]] || fail "Could not resolve version (check network connectivity or NTN_BASE_URL)"
readonly ARCHIVE_NAME="ntn-${NTN_TARGET}.tar.gz"
readonly ARCHIVE_URL="${BASE_URL}/releases/${VERSION}/${ARCHIVE_NAME}"
readonly CHECKSUM_URL="${ARCHIVE_URL}.sha256"

TMP_DIR="$(mktemp -d)"
readonly TMP_DIR
trap 'rm -rf "${TMP_DIR}"' EXIT

ARCHIVE_PATH="${TMP_DIR}/${ARCHIVE_NAME}"
readonly ARCHIVE_PATH
CHECKSUM_PATH="${TMP_DIR}/${ARCHIVE_NAME}.sha256"
readonly CHECKSUM_PATH

info "Downloading ${VERSION} for ${NTN_PLATFORM_LABEL}"
if ! download "${ARCHIVE_URL}" "${ARCHIVE_PATH}"; then
	rm -f "${ARCHIVE_PATH}"
	fail "Failed to download ${ARCHIVE_URL}"
fi

if ! download "${CHECKSUM_URL}" "${CHECKSUM_PATH}"; then
	rm -f "${CHECKSUM_PATH}"
	fail "Failed to download checksum file from ${CHECKSUM_URL}"
fi

verify_archive "${TMP_DIR}" "${ARCHIVE_NAME}" "$(basename "${CHECKSUM_PATH}")"

tar -xzf "${ARCHIVE_PATH}" -C "${TMP_DIR}"

BINARY_PATH="${TMP_DIR}/ntn-${NTN_TARGET}/ntn"
readonly BINARY_PATH
[[ -f "${BINARY_PATH}" ]] || fail "Downloaded archive did not contain an ntn binary"

install_binary "${BINARY_PATH}"

cat >&2 <<'BANNER'

  ▄▄▄▄▄▄▄▄▖
 ██▄▄▄▄▄▄▄▟▙▖
 ███ ▄▄  ▄▄▐▌
 ███ ▐█▙ ▐▌▐▌
 ███ ▐▌▜▙▐▌▐▌
 ███ ▟▙ ▀█▌▐▌
  ▀█▄▄▄▄▄▄▄▞▘

BANNER

printf '  Notion CLI (Beta)\n\n' >&2
printf '  Installed ntn %s to %s/ntn\n\n' "${VERSION}" "${INSTALL_DIR}" >&2

if [[ "${INSTALL_DIR_NEEDS_PATH_INSTRUCTIONS}" == "true" ]]; then
	cat >&2 <<EOF
  Add ntn to your PATH:

    export PATH="${INSTALL_DIR}:\$PATH"

  For zsh, add that line to ~/.zshrc.
  For bash, add that line to ~/.bashrc or ~/.bash_profile.

EOF
fi

cat >&2 <<'NEXT'
  Get started:

    ntn login              Log in to your Notion workspace
    ntn workers new        Create a new worker
    ntn datasources query  Query a data source
    ntn pages create       Create a page from Markdown
    ntn api                Call the Notion API directly
    ntn --help             See all available commands

  Add the Notion skill for your agents:

    npx skills add makenotion/skills

NEXT
