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

tool_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_dir="$(cd "$tool_dir/../.." && pwd)"
device_tool="$tool_dir/device.sh"
install_tool="$tool_dir/install.sh"
python_bin="$tool_dir/.venv/bin/python"

REMOTE_HOST="${REMOTE_HOST:-lighthouse@claw.pangtiao.xyz}"
REMOTE_REPO="${REMOTE_REPO:-/home/lighthouse/esp32-dev}"
BOARD="${BOARD:-m5stack-stopwatch}"

artifact_dir="$repo_dir/dist/$BOARD"
archive_dir=""
staging_dir=""
artifact_installed=0
open_monitor=1

die() {
    echo "$*" >&2
    exit 1
}

cleanup() {
    local status=$?
    trap - EXIT

    if [[ $status -ne 0 && $artifact_installed -eq 0 && -n "$archive_dir" && -d "$archive_dir" && ! -e "$artifact_dir" ]]; then
        if mv "$archive_dir" "$artifact_dir"; then
            echo "Restored previous local artifact after update failure: $artifact_dir" >&2
            archive_dir=""
        else
            echo "Could not restore the previous artifact; it remains at: $archive_dir" >&2
        fi
    fi

    if [[ -n "$staging_dir" && -d "$staging_dir" ]]; then
        rm -rf -- "$staging_dir"
    fi

    exit "$status"
}
trap cleanup EXIT

require_command() {
    command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}

read_version() {
    local directory="$1"
    local version_file="$directory/VERSION"
    local version=""

    if [[ -f "$version_file" ]]; then
        IFS= read -r version < "$version_file" || true
    fi
    if [[ -n "$version" ]]; then
        printf '%s\n' "$version"
    else
        printf '%s\n' "unknown"
    fi
}

preflight() {
    if [[ $# -eq 1 && "$1" == "--no-monitor" ]]; then
        open_monitor=0
    elif [[ $# -ne 0 ]]; then
        die "Usage: ./tools/mac/update-flash.sh [--no-monitor]"
    fi
    [[ "$BOARD" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || die "Invalid BOARD: $BOARD"
    [[ -n "$REMOTE_HOST" ]] || die "REMOTE_HOST must not be empty."
    [[ -n "$REMOTE_REPO" ]] || die "REMOTE_REPO must not be empty."
    [[ -x "$device_tool" ]] || die "Device tool is missing or not executable: $device_tool"
    [[ -x "$install_tool" ]] || die "Install tool is missing or not executable: $install_tool"

    require_command git
    require_command scp

    local git_root branch changes
    git_root="$(git -C "$repo_dir" rev-parse --show-toplevel 2>/dev/null)" || die "Not a Git repository: $repo_dir"
    [[ "$git_root" == "$repo_dir" ]] || die "Script repository root mismatch: expected $repo_dir, found $git_root"

    branch="$(git -C "$repo_dir" symbolic-ref --quiet --short HEAD 2>/dev/null)" || die "Detached HEAD is not supported. Check out main before updating."
    [[ "$branch" == "main" ]] || die "Current branch is '$branch', not 'main'. Switch to main before updating."

    changes="$(git -C "$repo_dir" status --porcelain --untracked-files=no --ignore-submodules=none)"
    if [[ -n "$changes" ]]; then
        echo "Local tracked changes detected." >&2
        echo "Commit/stash them before updating." >&2
        printf '%s\n' "$changes" >&2
        exit 1
    fi

    echo "Preflight: READY"
}

update_source() {
    local before after
    before="$(git -C "$repo_dir" rev-parse HEAD)"
    echo "Local commit before: $before"
    git -C "$repo_dir" fetch origin
    git -C "$repo_dir" pull --ff-only origin main
    after="$(git -C "$repo_dir" rev-parse HEAD)"
    echo "Local commit after:  $after"
}

ensure_tools() {
    if [[ -x "$python_bin" ]] && "$python_bin" -c 'import esptool, serial' >/dev/null 2>&1; then
        echo "Mac flash tools: READY"
        return
    fi

    echo "Mac flash tools missing/broken."
    echo "Running tools/mac/install.sh..."
    "$install_tool"

    [[ -x "$python_bin" ]] || die "Mac flash tools installation did not create $python_bin"
    "$python_bin" -c 'import esptool, serial' >/dev/null 2>&1 || die "Mac flash tools are still unavailable after installation."
    echo "Mac flash tools: READY"
}

archive_current_artifact() {
    local old_version="$1"
    local timestamp safe_version candidate

    if [[ ! -e "$artifact_dir" ]]; then
        echo "Current local artifact: none"
        return
    fi
    [[ -d "$artifact_dir" ]] || die "Local artifact path is not a directory: $artifact_dir"

    timestamp="$(date -u +%Y%m%d-%H%M%S)"
    safe_version="${old_version//[^A-Za-z0-9._-]/_}"
    candidate="$repo_dir/dist/archive/${BOARD}-${safe_version}-${timestamp}"
    if [[ -e "$candidate" ]]; then
        candidate="$candidate-$$"
    fi
    mkdir -p "$(dirname "$candidate")"
    mv "$artifact_dir" "$candidate"
    archive_dir="$candidate"
    echo "Archived current local artifact: $archive_dir"
}

download_artifact() {
    local remote_source downloaded

    mkdir -p "$repo_dir/dist"
    staging_dir="$repo_dir/dist/.update-flash-${BOARD}-$$"
    [[ ! -e "$staging_dir" ]] || die "Temporary download path already exists: $staging_dir"
    mkdir "$staging_dir"

    remote_source="${REMOTE_HOST}:${REMOTE_REPO%/}/dist/${BOARD}"
    echo "Downloading latest artifact: $remote_source"
    scp -r "$remote_source" "$staging_dir/"

    downloaded="$staging_dir/$BOARD"
    [[ -d "$downloaded" ]] || die "Downloaded artifact directory not found: $downloaded"
    "$device_tool" verify "$downloaded"
    [[ -s "$downloaded/VERSION" ]] || die "Downloaded artifact has no VERSION file."

    mv "$downloaded" "$artifact_dir"
    artifact_installed=1
    echo "Installed downloaded artifact: $artifact_dir"
}

confirm_flash() {
    local answer
    if ! read -r -p "Flash $BOARD now? [y/N] " answer; then
        die "Flash cancelled."
    fi
    [[ "$answer" == "y" || "$answer" == "Y" ]] || die "Flash cancelled."
}

main() {
    preflight "$@"
    update_source
    ensure_tools

    local old_version new_version
    old_version="$(read_version "$artifact_dir")"
    archive_current_artifact "$old_version"
    download_artifact

    "$device_tool" detect
    new_version="$(read_version "$artifact_dir")"
    echo "Old firmware artifact version: $old_version"
    echo "New firmware artifact version: $new_version"
    confirm_flash

    "$device_tool" flash "$artifact_dir" --yes
    if [[ $open_monitor -eq 1 ]]; then
        "$device_tool" monitor
    fi
}

main "$@"
