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

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
INPUT_PATH="${1:-${ROOT_DIR}}"
REGISTRY="${CLAWHUB_REGISTRY:-${SKILLHUB_REGISTRY:-http://127.0.0.1:8080}}"
SITE="${CLAWHUB_SITE:-${SKILLHUB_PUBLIC_BASE_URL:-${REGISTRY}}}"
IMAGE="${CLAWHUB_CLI_IMAGE:-skillhub/clawhub-cli:latest}"
WAIT_SECONDS="${WAIT_SECONDS:-120}"
ONLY_NAMESPACE="${ONLY_NAMESPACE:-}"
PUBLISH_TAGS="${PUBLISH_TAGS:-latest}"
ON_PUBLISH_WARNING="${ON_PUBLISH_WARNING:-skip}"
EXTRA_DOCKER_ARGS="${EXTRA_DOCKER_ARGS:-}"
CLAWHUB_DOCKER_CONFIG_DIR="${CLAWHUB_DOCKER_CONFIG_DIR:-${ROOT_DIR}/.clawhub-docker-config}"
SKIPPED_WARNINGS_FILE="${SKIPPED_WARNINGS_FILE:-${ROOT_DIR}/reports/offline-publish-skipped-warnings.log}"
TMP_DIR=""

usage() {
  cat >&2 <<'EOF'
Usage:
  CLAWHUB_REGISTRY=http://skillhub.example.com:8080 CLAWHUB_TOKEN=clh_xxx \
    bash scripts/deploy-offline-skills-docker.sh [offline-pack.tar.gz|skills-dir]

Environment:
  CLAWHUB_REGISTRY    SkillHub API base URL. Default: http://127.0.0.1:8080
  CLAWHUB_SITE        SkillHub public URL for official CLI login/config. Default: CLAWHUB_REGISTRY
  CLAWHUB_TOKEN       API token consumed by "clawhub login --token" inside the CLI container.
  CLAWHUB_CLI_IMAGE   Docker image to run. Default: skillhub/clawhub-cli:latest
  ONLY_NAMESPACE      Publish only one namespace from _sources.json.
  PUBLISH_TAGS        Comma-separated tags for official clawhub publish. Default: latest
  ON_PUBLISH_WARNING  What to do when SkillHub returns pre-publish warnings:
                      skip or fail. Default: skip
  SKIPPED_WARNINGS_FILE
                      Log file for skipped warning-blocked skills.
  WAIT_SECONDS        Backend health wait timeout. Default: 120
  CLAWHUB_DOCKER_CONFIG_DIR
                      Host directory for the official CLI config. Default: ./.clawhub-docker-config

Examples:
  bash scripts/load-clawhub-cli-image.sh images/clawhub-cli-latest.tar
  CLAWHUB_REGISTRY=http://10.0.0.8:8080 CLAWHUB_TOKEN=clh_xxx \
    bash scripts/deploy-offline-skills-docker.sh artifacts/github-skills-offline-20260605.tar.gz
EOF
}

cleanup() {
  if [ -n "${TMP_DIR}" ] && [ -d "${TMP_DIR}" ]; then
    rm -rf "${TMP_DIR}"
  fi
}
trap cleanup EXIT

if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
  usage
  exit 0
fi

resolve_skills_root() {
  local input="$1"

  if [ -f "${input}" ]; then
    case "${input}" in
      *.tar.gz|*.tgz) ;;
      *)
        echo "Unsupported offline pack format: ${input}" >&2
        exit 2
        ;;
    esac

    if [ -f "${input}.sha256" ]; then
      local expected
      local actual
      expected="$(awk '{print $1}' "${input}.sha256")"
      actual="$(sha256sum "${input}" | awk '{print $1}')"
      if [ "${expected}" != "${actual}" ]; then
        echo "Checksum mismatch for ${input}" >&2
        exit 1
      fi
      echo "${input}: OK" >&2
    fi

    TMP_DIR="$(mktemp -d)"
    tar -C "${TMP_DIR}" -xzf "${input}"
    local packed_root
    packed_root="$(find "${TMP_DIR}/seed-skills" -mindepth 1 -maxdepth 1 -type d -name '*' -print 2>/dev/null | while read -r candidate; do
      if [ -f "${candidate}/_sources.json" ]; then
        echo "${candidate}"
        break
      fi
    done)"
    if [ -z "${packed_root}" ]; then
      echo "Offline pack does not contain a seed-skills/<pack> directory with _sources.json: ${input}" >&2
      exit 1
    fi
    echo "${packed_root}"
    return
  fi

  if [ -d "${input}/seed-skills" ]; then
    local unpacked_root
    unpacked_root="$(find "${input}/seed-skills" -mindepth 1 -maxdepth 1 -type d -print 2>/dev/null | while read -r candidate; do
      if [ -f "${candidate}/_sources.json" ]; then
        echo "${candidate}"
        break
      fi
    done)"
    if [ -n "${unpacked_root}" ]; then
      echo "${unpacked_root}"
      return
    fi
  fi

  if [ -d "${input}" ] && [ -f "${input}/_sources.json" ]; then
    echo "${input}"
    return
  fi

  if [ -d "${input}" ]; then
    echo "${input}"
    return
  fi

  echo "Offline skills input not found: ${input}" >&2
  exit 1
}

health_check() {
  local url="${REGISTRY%/}/actuator/health"
  local attempts=$((WAIT_SECONDS / 2))
  [ "${attempts}" -lt 1 ] && attempts=1

  echo "Checking SkillHub backend: ${url}"
  for _ in $(seq 1 "${attempts}"); do
    if command -v curl >/dev/null 2>&1; then
      if curl -fsS "${url}" >/dev/null 2>&1; then
        return
      fi
    elif command -v wget >/dev/null 2>&1; then
      if wget -qO- "${url}" >/dev/null 2>&1; then
        return
      fi
    else
      echo "Neither curl nor wget is available; skipping host health check."
      return
    fi
    sleep 2
  done

  echo "SkillHub backend is not healthy after ${WAIT_SECONDS}s: ${url}" >&2
  exit 1
}

namespace_for_source_dir() {
  local sources_json="$1"
  local source_name="$2"

  if [ ! -f "${sources_json}" ] || ! command -v python3 >/dev/null 2>&1; then
    echo "${source_name}"
    return
  fi

  python3 - "${sources_json}" "${source_name}" <<'PY'
import json
import re
import sys

sources_json, source_name = sys.argv[1], sys.argv[2]

def slugify(value):
    value = value.lower().replace("_", "-").replace("/", "--")
    value = re.sub(r"[^a-z0-9.-]+", "-", value)
    value = re.sub(r"-{2,}", "-", value).strip(".-")
    return value

try:
    sources = json.load(open(sources_json, encoding="utf-8"))
except Exception:
    print(source_name)
    raise SystemExit(0)

for item in sources:
    source_key = item.get("repo") or item.get("source") or ""
    if slugify(source_key) == source_name:
        print(item.get("namespace") or source_name)
        break
else:
    print(source_name)
PY
}

json_field() {
  local file="$1"
  local field="$2"
  local fallback="$3"
  if command -v python3 >/dev/null 2>&1; then
    python3 - "${file}" "${field}" "${fallback}" <<'PY'
import json
import sys
path, field, fallback = sys.argv[1], sys.argv[2], sys.argv[3]
try:
    value = json.load(open(path, encoding="utf-8")).get(field) or fallback
except Exception:
    value = fallback
print(value)
PY
  else
    echo "${fallback}"
  fi
}

slugify() {
  local value="$1"
  printf '%s' "${value}" \
    | tr '[:upper:]_' '[:lower:]-' \
    | sed -E 's/[^a-z0-9.-]+/-/g; s/-+/-/g; s/^-//; s/-$//'
}

run_clawhub() {
  local mount_dir="$1"
  shift
  mkdir -p "${CLAWHUB_DOCKER_CONFIG_DIR}"
  # shellcheck disable=SC2086
  docker run --rm \
    --network host \
    -e "XDG_CONFIG_HOME=/clawhub-config" \
    -e "CLAWHUB_REGISTRY=${REGISTRY}" \
    -e "CLAWHUB_SITE=${SITE}" \
    -e "NO_PROXY=${NO_PROXY:-localhost,127.0.0.1}" \
    -e "no_proxy=${no_proxy:-localhost,127.0.0.1}" \
    ${CLAWHUB_TOKEN:+-e "CLAWHUB_TOKEN=${CLAWHUB_TOKEN}"} \
    ${HTTP_PROXY:+-e "HTTP_PROXY=${HTTP_PROXY}"} \
    ${HTTPS_PROXY:+-e "HTTPS_PROXY=${HTTPS_PROXY}"} \
    ${http_proxy:+-e "http_proxy=${http_proxy}"} \
    ${https_proxy:+-e "https_proxy=${https_proxy}"} \
    ${EXTRA_DOCKER_ARGS} \
    -v "${CLAWHUB_DOCKER_CONFIG_DIR}:/clawhub-config" \
    -v "${mount_dir}:/workspace/skill:ro" \
    "${IMAGE}" \
    --registry "${REGISTRY}" \
    --site "${SITE}" \
    --no-input \
    "$@"
}

login_if_token_present() {
  local mount_dir="$1"
  if [ -z "${CLAWHUB_TOKEN:-}" ]; then
    echo "CLAWHUB_TOKEN is not set. The official CLI may fail when publishing authenticated endpoints." >&2
    return
  fi

  echo "Checking official clawhub CLI image: ${IMAGE}"
  docker run --rm "${IMAGE}" --help >/dev/null
  echo "Logging in official clawhub CLI with token"
  run_clawhub "${mount_dir}" login --token "${CLAWHUB_TOKEN}" >/dev/null
}

publish_skill() {
  local skill_dir="$1"
  local namespace="$2"
  local package_json="${skill_dir}/package.json"
  local package_name
  local package_version
  local display_name
  local compat_slug
  local output

  package_name="$(json_field "${package_json}" name "$(basename "${skill_dir}")")"
  package_version="$(json_field "${package_json}" version "0.1.0")"
  display_name="$(json_field "${package_json}" description "${package_name}")"
  compat_slug="$(slugify "${namespace}--${package_name}")"

  echo "Publishing ${compat_slug}@${package_version}"

  # SkillHub v0.2.x documents namespace-aware ClawHub compatibility through
  # canonical slugs in the form namespace--skill.
  if output="$(run_clawhub "${skill_dir}" publish /workspace/skill \
    --slug "${compat_slug}" \
    --name "${display_name}" \
    --version "${package_version}" \
    --tags "${PUBLISH_TAGS}" 2>&1)"; then
    printf '%s\n' "${output}"
    return
  fi
  printf '%s\n' "${output}" >&2
  if printf '%s\n' "${output}" | grep -q "Pre-publish warnings require confirmation"; then
    handle_publish_warning "${skill_dir}" "${compat_slug}" "${output}"
    return $?
  fi

  echo "Legacy publish flags failed; retrying official skill publish form." >&2
  if output="$(run_clawhub "${skill_dir}" skill publish /workspace/skill \
    --version "${package_version}" 2>&1)"; then
    printf '%s\n' "${output}"
    return
  fi
  printf '%s\n' "${output}" >&2
  if printf '%s\n' "${output}" | grep -q "Pre-publish warnings require confirmation"; then
    handle_publish_warning "${skill_dir}" "${compat_slug}" "${output}"
    return $?
  fi
  return 1
}

handle_publish_warning() {
  local skill_dir="$1"
  local compat_slug="$2"
  local output="$3"

  if [ "${ON_PUBLISH_WARNING}" = "fail" ]; then
    echo "Publish blocked by pre-publish warnings for ${compat_slug}" >&2
    return 1
  fi

  if [ "${ON_PUBLISH_WARNING}" != "skip" ]; then
    echo "Unsupported ON_PUBLISH_WARNING=${ON_PUBLISH_WARNING}; expected skip or fail." >&2
    return 1
  fi

  mkdir -p "$(dirname "${SKIPPED_WARNINGS_FILE}")"
  {
    echo "=== ${compat_slug} ==="
    echo "path: ${skill_dir}"
    printf '%s\n' "${output}"
    echo
  } >> "${SKIPPED_WARNINGS_FILE}"
  echo "Skipped ${compat_slug}: pre-publish warnings require review. Details: ${SKIPPED_WARNINGS_FILE}" >&2
  return 10
}

publish_source_dir() {
  local source_dir="$1"
  local namespace="$2"
  local count=0
  local skipped=0
  local status=0

  echo "Publishing namespace ${namespace} from ${source_dir}"
  while IFS= read -r skill_dir; do
    [ -f "${skill_dir}/package.json" ] || continue
    [ -f "${skill_dir}/skill.md" ] || continue
    if publish_skill "${skill_dir}" "${namespace}"; then
      count=$((count + 1))
    else
      status=$?
      if [ "${status}" -eq 10 ]; then
        skipped=$((skipped + 1))
      else
        return "${status}"
      fi
    fi
  done < <(find "${source_dir}" -mindepth 1 -maxdepth 1 -type d | sort)

  echo "Published ${count} skills to namespace ${namespace}; skipped ${skipped}"
}

main() {
  local skills_root
  local sources_json
  local total=0

  skills_root="$(resolve_skills_root "${INPUT_PATH}")"
  health_check
  login_if_token_present "${skills_root}"

  sources_json="${skills_root}/_sources.json"
  while IFS= read -r source_dir; do
    local source_name
    local namespace
    source_name="$(basename "${source_dir}")"
    namespace="$(namespace_for_source_dir "${sources_json}" "${source_name}")"

    if [ -n "${ONLY_NAMESPACE}" ] && [ "${namespace}" != "${ONLY_NAMESPACE}" ]; then
      continue
    fi

    publish_source_dir "${source_dir}" "${namespace}"
    total=$((total + 1))
  done < <(find "${skills_root}" -mindepth 1 -maxdepth 1 -type d | sort)

  if [ "${total}" -eq 0 ]; then
    echo "No source directories were published from ${skills_root}" >&2
    exit 1
  fi

  echo "Offline skill deployment complete via Docker image ${IMAGE}. Registry: ${REGISTRY}"
}

main
