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

COMPOSE_FILE_NAME="docker-compose.yml"
COMPOSE_FILE=""
DOCKER_COMPOSE_PATH="${DOCKER_COMPOSE_PATH:-/www/atkeylogin}"
SILENT=false

while [ "$#" -gt 0 ]; do
    case "$1" in
        --silent)
            SILENT=true
            ;;
        --slient)
            SILENT=true
            ;;
        *)
            echo "Error: unknown argument: $1" >&2
            exit 1
            ;;
    esac
    shift
done

find_docker_compose() {
    if docker compose version >/dev/null 2>&1; then
        echo "docker compose"
        return 0
    fi

    if command -v docker-compose >/dev/null 2>&1; then
        echo "docker-compose"
        return 0
    fi

    echo "Error: docker compose command not found." >&2
    return 1
}

prompt_required() {
    local prompt="$1"
    local value

    while true; do
        read -r -p "$prompt" value
        if [ -n "$value" ]; then
            printf '%s\n' "$value"
            return 0
        fi
        echo "Value cannot be empty."
    done
}

require_root() {
    if [ "$(id -u)" -ne 0 ]; then
        echo "Error: this script must be executed with root permission. Please run with sudo."
        exit 1
    fi
}

resolve_compose_file() {
    local compose_path="$1"
    local candidate

    if [ -f "$compose_path" ]; then
        COMPOSE_FILE="$compose_path"
        DOCKER_COMPOSE_PATH="$(dirname -- "$compose_path")"
        return 0
    fi

    compose_path="${compose_path%/}"
    candidate="${compose_path}/${COMPOSE_FILE_NAME}"
    if [ -f "$candidate" ]; then
        DOCKER_COMPOSE_PATH="$compose_path"
        COMPOSE_FILE="$candidate"
        return 0
    fi

    echo "Could not find $candidate."
    compose_path="$(prompt_required "Docker compose path: ")"

    if [ -f "$compose_path" ]; then
        COMPOSE_FILE="$compose_path"
        DOCKER_COMPOSE_PATH="$(dirname -- "$compose_path")"
        return 0
    fi

    compose_path="${compose_path%/}"
    candidate="${compose_path}/${COMPOSE_FILE_NAME}"
    if [ ! -f "$candidate" ]; then
        echo "Error: $candidate does not exist."
        exit 1
    fi

    DOCKER_COMPOSE_PATH="$compose_path"
    COMPOSE_FILE="$candidate"
}

validate_image_digest() {
    local image="$1"

    if ! printf '%s\n' "$image" | grep -Eq '^.+@sha256:[0-9a-fA-F]{64}$'; then
        echo "Error: image hash must look like registry/name@sha256:<64 hex chars>." >&2
        exit 1
    fi
}

validate_version() {
    local version="$1"

    if ! printf '%s\n' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$'; then
        echo "Error: version must look like 2.3.6 or 2.3.6-rc.1." >&2
        exit 1
    fi
}

write_version_update_script() {
    local image="$1"
    local version="$2"
    local script_file="update_docker_image_${version}.sh"

    cat > "$script_file" <<EOF
#!/usr/bin/env bash
set -euo pipefail

IMAGE_HASH='$image'
VERSION='$version'

SCRIPT_DIR="\$(CDPATH= cd -- "\$(dirname -- "\$0")" && pwd)"
cd "\$SCRIPT_DIR"

IMAGE_HASH="\$IMAGE_HASH" VERSION="\$VERSION" ./update_docker_image.sh
EOF

    chmod +x "$script_file"
    echo "Version update script saved at $script_file"
}

read_current_app_image() {
    awk '
        function indent_len(line) {
            match(line, /^[[:space:]]*/)
            return RLENGTH
        }

        /^services:[[:space:]]*$/ {
            in_services = 1
            services_indent = indent_len($0)
            next
        }

        in_services && indent_len($0) == services_indent + 2 && $0 ~ /^[[:space:]]{2}app:[[:space:]]*$/ {
            in_app = 1
            app_indent = substr($0, 1, indent_len($0))
            next
        }

        in_app && indent_len($0) <= length(app_indent) && $0 !~ /^[[:space:]]*$/ {
            in_app = 0
        }

        in_app && $0 ~ "^[[:space:]]*" app_indent "  image:[[:space:]]*" {
            sub(/^[[:space:]]*image:[[:space:]]*/, "")
            sub(/[[:space:]]*#.*/, "")
            gsub(/^[[:space:]]+|[[:space:]]+$/, "")
            print
            exit
        }
    ' "$COMPOSE_FILE"
}

remove_app_image() {
    local image="$1"
    local new_image="$2"

    if [ -z "$image" ]; then
        echo "No active services.app image found to delete."
        return 0
    fi

    if [ "$image" = "$new_image" ]; then
        echo "Current app image is the same as the new image. Skipping image delete."
        return 0
    fi

    echo "Deleting previous app image: $image"
    docker image rm "$image" || echo "Previous app image was not removed. Continuing."
}

remove_app_container() {
    echo "Removing stopped app container..."
    $DOCKER_COMPOSE -f "$COMPOSE_FILE" rm -f app || echo "Stopped app container was not removed. Continuing."
}

update_compose_image() {
    local image="$1"
    local version="$2"
    local tmp_file="${COMPOSE_FILE}.tmp"

    awk -v image="$image" -v version="$version" '
        function indent_len(line) {
            match(line, /^[[:space:]]*/)
            return RLENGTH
        }

        function emit_app_image() {
            if (in_app && !inserted) {
                print app_indent "  # version: " version
                print app_indent "  image: " image
                inserted = 1
            }
            pending_image = 0
        }

        /^services:[[:space:]]*$/ {
            if (in_app) {
                emit_app_image()
            }
            in_services = 1
            services_indent = indent_len($0)
            print
            next
        }

        in_services && indent_len($0) == services_indent + 2 && $0 ~ /^[[:space:]]{2}app:[[:space:]]*$/ {
            if (in_app) {
                emit_app_image()
            }
            in_app = 1
            app_indent = substr($0, 1, indent_len($0))
            found_app = 1
            print
            next
        }

        in_app && indent_len($0) <= length(app_indent) && $0 !~ /^[[:space:]]*$/ {
            emit_app_image()
            in_app = 0
        }

        in_app && $0 ~ "^[[:space:]]*" app_indent "  image:[[:space:]]*" {
            print app_indent "  # " substr($0, length(app_indent) + 3)
            pending_image = 1
            next
        }

        in_app && $0 ~ "^[[:space:]]*" app_indent "  #[[:space:]]*image:[[:space:]]*" {
            pending_image = 1
            print
            next
        }

        in_app && pending_image && $0 !~ "^[[:space:]]*" app_indent "  #[[:space:]]*image:[[:space:]]*" {
            emit_app_image()
        }

        {
            print
        }

        END {
            if (in_app) {
                emit_app_image()
            }
            if (!found_app) {
                exit 2
            }
            if (!inserted) {
                exit 3
            }
        }
    ' "$COMPOSE_FILE" > "$tmp_file" || {
        local status=$?
        rm -f "$tmp_file"
        case "$status" in
            2) echo "Error: services.app was not found in $COMPOSE_FILE." >&2 ;;
            3) echo "Error: unable to insert image under services.app." >&2 ;;
            *) echo "Error: failed to update $COMPOSE_FILE." >&2 ;;
        esac
        exit 1
    }

    mv "$tmp_file" "$COMPOSE_FILE"
}

IMAGE_HASH="${IMAGE_HASH:-}"
VERSION="${VERSION:-}"

if [ -z "$IMAGE_HASH" ]; then
    IMAGE_HASH="$(prompt_required "Image hash: ")"
fi

if [ -z "$VERSION" ]; then
    VERSION="$(prompt_required "Version: ")"
fi

validate_image_digest "$IMAGE_HASH"
validate_version "$VERSION"

if [ "$SILENT" = true ]; then
    write_version_update_script "$IMAGE_HASH" "$VERSION"
    exit 0
fi

require_root
resolve_compose_file "$DOCKER_COMPOSE_PATH"
DOCKER_COMPOSE="$(find_docker_compose)"
CURRENT_APP_IMAGE="$(read_current_app_image)"

echo "Stopping current docker compose services..."
$DOCKER_COMPOSE -f "$COMPOSE_FILE" stop
remove_app_container

echo "Updating services.app image in $COMPOSE_FILE..."
cp "$COMPOSE_FILE" "${COMPOSE_FILE}.bak"
update_compose_image "$IMAGE_HASH" "$VERSION"
write_version_update_script "$IMAGE_HASH" "$VERSION"

echo "Pulling app image..."
$DOCKER_COMPOSE -f "$COMPOSE_FILE" pull app

echo "Starting docker compose services..."
$DOCKER_COMPOSE -f "$COMPOSE_FILE" up -d
remove_app_image "$CURRENT_APP_IMAGE" "$IMAGE_HASH"

echo "Done. Backup saved at ${COMPOSE_FILE}.bak"
