#!/bin/bash
pending_commit=$(ostree admin status | grep "pending" | awk '{print $2}')
checkout_commit=$(ostree admin status | grep "pending" | cut -d'.' -f1 | awk '{print $2}')
osname=$(ostree admin status | grep "pending" | cut -d'.' -f1 | awk '{print $1}')

echo "Detected pending commit: $pending_commit"

#-------------------------------检出 json文件到 /tmp中 -------------------------------
ostree --subpath=/var/opt/system/resource/update-manifests checkout --union -C $checkout_commit /tmp/update-manifests-json

MANIFEST_DIR="/tmp/update-manifests-json"
echo "Manifest checkout directory: $MANIFEST_DIR"
if [ ! -d "$MANIFEST_DIR" ]; then
  echo "Warning: manifest checkout directory missing: $MANIFEST_DIR" >&2
else
  _json_count=0
  while IFS= read -r f; do
    echo "  checked-out json: $f"
    _json_count=$((_json_count + 1))
  done < <(find "$MANIFEST_DIR" -maxdepth 1 -type f -name '*.json' | sort)
  if [ "$_json_count" -eq 0 ]; then
    echo "  (no *.json at top level of $MANIFEST_DIR)"
  else
    echo "  total: $_json_count JSON file(s)"
  fi
fi
echo "Update update manifests completed."

#-------------------------------通过json文件对文件进行更新 -------------------------------
DEPLOY_BASE="/ostree/deploy/kylin/deploy/${pending_commit}"
OVL_BASE="/ostree/pkgs/ovl-${pending_commit}"
VAR_OPT="/var"

TARGET_DIR="${TARGET_DIR:-}"
TARGET_ETC_UPPER="${OVL_BASE}/etc-ovl/etc-upper"
TARGET_VAR_UPPER="${OVL_BASE}/var-ovl/var-upper"

#MANIFEST_DIR="${MANIFEST_DIR:-${DEPLOY_BASE}/var/opt/system/resource/update-manifests}"
STAGING_DIR="${STAGING_DIR:-/tmp/update-manifests}"

# 清单绝对路径 -> ostree 仓库中的 --subpath（/etc* -> /usr/etc*）
ostree_subpath_for_manifest_path() {
    local abs="$1"
    case "$abs" in
        /etc/*|/etc)
            printf '%s\n' "/usr${abs}"
            ;;
        *)
            printf '%s\n' "$abs"
            ;;
    esac
}

resolve_target_for_abs_path() {
    local abs="$1"

    if [ -n "$TARGET_DIR" ]; then
        printf '%s%s\n' "${TARGET_DIR%/}" "$abs"
        return 0
    fi

    case "$abs" in
        /etc/*)
            # overlay 上层根已对应 /etc，拼接时去掉前缀 /etc/，避免出现 .../etc-upper/etc/...
            printf '%s/%s\n' "${TARGET_ETC_UPPER%/}" "${abs#/etc/}"
            ;;
        /etc)
            printf '%s\n' "${TARGET_ETC_UPPER%/}"
            ;;
        /var/lib*|/var/lib)
            # var-upper 已表示 /var 的上层，拼接时去掉前缀 /var/，避免出现 .../var-upper/var/...
            printf '%s/%s\n' "${TARGET_VAR_UPPER%/}" "${abs#/var/}"
            ;;
        /var/opt*|/var/opt)
            printf '%s/%s\n' "${VAR_OPT%/}" "${abs#/var/}"
            ;;
        *)
            echo "Error: path must be absolute: $abs" >&2
            return 1
            ;;
    esac
}

# 通过 ostree ls 判断仓库 subpath 是目录还是普通文件
is_repo_subpath_directory() {
    local commit="$1"
    local subpath="$2"
    local parent="${subpath%/*}"
    local base="${subpath##*/}"
    local line

    [ -n "$base" ] || return 1
    if [ "$parent" = "$subpath" ]; then
        parent="/"
    fi

    line=$(ostree ls "$commit" "$parent" 2>/dev/null | awk -v b="$base" '
        {
            p = $NF
            if (p == b || p ~ ("/" b "$")) {
                print $0
                exit
            }
        }')
    [ -n "$line" ] && [[ "$line" == d* ]]
}

copy_checked_out_file() {
    local src="$1"
    local manifest_abs="$2"
    local dst_file dst_parent missing_parent

    dst_file=$(resolve_target_for_abs_path "$manifest_abs") || return 1
    dst_parent=$(dirname -- "$dst_file")
    missing_parent=0
    if [ ! -d "$dst_parent" ]; then
        missing_parent=1
    fi
    mkdir -p -- "$dst_parent" || {
        echo "Error: cannot create directory: $dst_parent" >&2
        return 1
    }
    if [ "$missing_parent" -eq 1 ]; then
        echo "Created destination directory tree: $dst_parent"
    fi
    # -a：保留软链接本身（不跟随），并保留权限/时间戳等属性
    if cp -af -- "$src" "$dst_file"; then
        echo "Successfully copied: $src -> $dst_file"
        if [ -L "$dst_file" ]; then
            echo "  symlink: $(readlink -- "$dst_file")"
        else
            md5sum -- "$dst_file"
        fi
        return 0
    fi
    echo "Error: Failed to copy $src -> $dst_file" >&2
    return 1
}

process_manifest_entry() {
    local abs_path="$1"
    local repo_subpath name staging_dest src rel n=0

    abs_path="${abs_path%/}"
    repo_subpath=$(ostree_subpath_for_manifest_path "$abs_path")
    name="${abs_path##*/}"

    echo "================ $abs_path (repo: $repo_subpath) ================"

    if is_repo_subpath_directory "$checkout_commit" "$repo_subpath"; then
        staging_dest="${STAGING_DIR%/}/${name}"
        rm -rf -- "$staging_dest"
        mkdir -p -- "$(dirname -- "$staging_dest")" || {
            echo "Error: cannot create staging parent: $(dirname -- "$staging_dest")" >&2
            return 1
        }
        if ! ostree --subpath="$repo_subpath" checkout --union -C "$checkout_commit" "$staging_dest"; then
            echo "Warning: ostree checkout failed for directory $repo_subpath" >&2
            return 1
        fi
        if [ ! -d "$staging_dest" ]; then
            echo "Warning: directory not found after checkout: $staging_dest" >&2
            return 1
        fi
        while IFS= read -r -d '' src; do
            rel="${src#$staging_dest/}"
            [ -n "$rel" ] || continue
            copy_checked_out_file "$src" "${abs_path}/${rel}" || true
            n=$((n + 1))
        done < <(find "$staging_dest" \( -type f -o -type l \) -print0)
        if [ "$n" -eq 0 ]; then
            echo "Warning: no files under directory checkout: $staging_dest" >&2
            return 1
        fi
        echo "Deployed $n file(s) from $staging_dest -> $abs_path"
        return 0
    fi

    staging_dest="${STAGING_DIR%/}"
    mkdir -p -- "$staging_dest" || {
        echo "Error: cannot create staging directory: $staging_dest" >&2
        return 1
    }
    if ! ostree --subpath="$repo_subpath" checkout --union -C "$checkout_commit" "$staging_dest"; then
        echo "Warning: ostree checkout failed for file $repo_subpath" >&2
        return 1
    fi
    src="${staging_dest}/${name}"
    if [ ! -e "$src" ] && [ ! -L "$src" ]; then
        echo "Warning: source file not found after checkout: $src" >&2
        return 1
    fi
    copy_checked_out_file "$src" "$abs_path"
}

# upgrade_directory：仅当本地对应路径不存在该文件时才检出并拷贝；跳过 .pyc 文件
process_upgrade_directory_entry() {
    local abs_path="$1"
    local repo_subpath name staging_dest src rel n=0 skipped=0

    abs_path="${abs_path%/}"
    repo_subpath=$(ostree_subpath_for_manifest_path "$abs_path")
    name="${abs_path##*/}"

    echo "================ $abs_path (repo: $repo_subpath) [upgrade_directory] ================"

    if ! is_repo_subpath_directory "$checkout_commit" "$repo_subpath"; then
        echo "Warning: upgrade_directory path is not a directory in repo: $repo_subpath" >&2
        return 1
    fi

    staging_dest="${STAGING_DIR%/}/${name}"
    rm -rf -- "$staging_dest"
    mkdir -p -- "$(dirname -- "$staging_dest")" || {
        echo "Error: cannot create staging parent: $(dirname -- "$staging_dest")" >&2
        return 1
    }
    if ! ostree --subpath="$repo_subpath" checkout --union -C "$checkout_commit" "$staging_dest"; then
        echo "Warning: ostree checkout failed for directory $repo_subpath" >&2
        return 1
    fi
    if [ ! -d "$staging_dest" ]; then
        echo "Warning: directory not found after checkout: $staging_dest" >&2
        return 1
    fi
    while IFS= read -r -d '' src; do
        rel="${src#$staging_dest/}"
        [ -n "$rel" ] || continue
        if [[ "$rel" == *.pyc ]]; then
            echo "Skipped (.pyc file): ${abs_path}/${rel}"
            skipped=$((skipped + 1))
            continue
        fi
        dst_file=$(resolve_target_for_abs_path "${abs_path}/${rel}") || continue
        # -e：存在；-L：含断链软链接（断链时 -e 为假）
        if [ -e "$dst_file" ] || [ -L "$dst_file" ]; then
            echo "Skipped (local file exists): $dst_file"
            skipped=$((skipped + 1))
            continue
        fi
        if copy_checked_out_file "$src" "${abs_path}/${rel}"; then
            n=$((n + 1))
        fi
    done < <(find "$staging_dest" \( -type f -o -type l \) -print0)
    if [ "$n" -eq 0 ] && [ "$skipped" -eq 0 ]; then
        echo "Warning: no files under directory checkout: $staging_dest" >&2
        return 1
    fi
    echo "Deployed $n new file(s), skipped $skipped file(s) from $staging_dest -> $abs_path"
    return 0
}

# bash 原生 case；/var/foo → "${TARGET_VAR_UPPER}/foo"，/var → 无前缀后缀
_collect_paths_py() {
    MANIFEST_DIR="$MANIFEST_DIR" python3 - <<'PY'
import glob
import json
import os
import sys

base = os.environ.get("MANIFEST_DIR", "").strip()
if not base:
    print("MANIFEST_DIR is empty", file=sys.stderr)
    sys.exit(1)

file_paths = set()
dir_paths = set()
errors = False

def collect_abs_paths(items, path, bucket):
    if not isinstance(items, list):
        return
    for item in items:
        if not isinstance(item, str):
            continue
        p = item.strip()
        if not p.startswith("/"):
            print(f"Warning: skip non-absolute path in {path}: {item!r}", file=sys.stderr)
            continue
        bucket.add(p)

for path in sorted(glob.glob(os.path.join(base, "*.json"))):
    try:
        with open(path, encoding="utf-8") as fp:
            data = json.load(fp)
    except OSError as e:
        print(f"Warning: cannot read {path}: {e}", file=sys.stderr)
        errors = True
        continue
    except json.JSONDecodeError as e:
        print(f"Warning: invalid JSON {path}: {e}", file=sys.stderr)
        errors = True
        continue

    manifests = data.get("manifests")
    if not isinstance(manifests, dict):
        continue
    collect_abs_paths(manifests.get("upgrade_files"), path, file_paths)
    collect_abs_paths(manifests.get("upgrade_directory"), path, dir_paths)

for p in sorted(file_paths):
    print(f"FILE:{p}")
for p in sorted(dir_paths):
    print(f"DIR:{p}")

if errors and not file_paths and not dir_paths:
    sys.exit(1)
PY
}

if ! command -v python3 >/dev/null 2>&1; then
    echo "Error: python3 is required to parse manifest JSON." >&2
    exit 1
fi

if [ ! -d "$MANIFEST_DIR" ]; then
    echo "Warning: manifest directory not found: $MANIFEST_DIR — nothing to copy." >&2
    exit 0
fi

path_stream=$(_collect_paths_py)
collect_status=$?
if [ "$collect_status" -ne 0 ]; then
    echo "Warning: failed to collect paths from $MANIFEST_DIR" >&2
    exit "$collect_status"
fi

if [ -z "$path_stream" ]; then
    echo "No upgrade_files or upgrade_directory paths found under $MANIFEST_DIR"
    exit 0
fi

if [ -z "$checkout_commit" ]; then
    echo "Error: checkout_commit is empty (ostree admin status pending?)." >&2
    exit 1
fi

echo "Ostree checkout commit: $checkout_commit (work dir: $STAGING_DIR)"
rm -rf -- "$STAGING_DIR"

while IFS= read -r entry || [ -n "$entry" ]; do
    [ -n "$entry" ] || continue
    case "$entry" in
        FILE:*)
            process_manifest_entry "${entry#FILE:}" || true
            ;;
        DIR:*)
            process_upgrade_directory_entry "${entry#DIR:}" || true
            ;;
        *)
            process_manifest_entry "$entry" || true
            ;;
    esac
done <<< "$path_stream"

echo "Update manifests completed."