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

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
go_work="$repo_root/go.work"
private_modules=(
  "github.com/pingcap-inc/nexuscrm-api-host"
  "github.com/pingcap-inc/nexuscrm-connectors"
  "github.com/pingcap-inc/nexuscrm-contract"
  "github.com/pingcap-inc/nexuscrm-controlplane"
)
workspace_go_mods=()
violations=0

if [[ ! -f "$go_work" ]]; then
  echo "::error::go.work is missing"
  exit 1
fi

for required_tool in go jq; do
  if ! command -v "$required_tool" >/dev/null 2>&1; then
    echo "::error::$required_tool is required to parse workspace module manifests"
    exit 1
  fi
done

manifest_cache="$(mktemp -d)"
trap 'rm -rf "$manifest_cache"' EXIT

if ! go_work_json="$(GOENV=off GOTOOLCHAIN=local go work edit -json "$go_work")"; then
  echo "::error file=go.work::Go could not parse the workspace manifest"
  exit 1
fi
if ! workspace_module_dirs="$(jq -r '.Use[]?.DiskPath' <<<"$go_work_json")"; then
  echo "::error file=go.work::could not read Go workspace module paths"
  exit 1
fi

# go.work is the owner of the code-bearing module inventory. Each listed
# module must also describe the private-module version selected when its local
# replacements are loaded with workspace mode disabled.
while IFS= read -r module_dir; do
  [[ -n "$module_dir" ]] || continue
  if [[ ! "$module_dir" =~ ^\./[A-Za-z0-9._/-]+$ ]] || \
     [[ "$module_dir" == *"/../"* ]] || \
     [[ "$module_dir" == "./.." ]]; then
    echo "::error file=go.work::unsupported workspace module path $module_dir"
    exit 1
  fi
  workspace_go_mods+=("${module_dir#./}/go.mod")
done <<<"$workspace_module_dirs"

if [[ "${#workspace_go_mods[@]}" -eq 0 ]]; then
  echo "::error file=go.work::workspace contains no modules"
  exit 1
fi

go_mod_index_file() {
  local go_mod_path="$1"
  local relative_path cache_file
  if [[ "$go_mod_path" != "$repo_root"/* ]]; then
    return 1
  fi
  relative_path="${go_mod_path#"$repo_root/"}"
  cache_file="$manifest_cache/$relative_path.tsv"
  if [[ ! -s "$cache_file" ]]; then
    mkdir -p "$(dirname "$cache_file")"
    if ! GOENV=off GOWORK=off GOTOOLCHAIN=local go mod edit -json "$go_mod_path" | \
      jq -r '
        . as $manifest
        | (["module", ($manifest.Module.Path // ""), "", "", ""] | @tsv),
          ($manifest.Require[]? | ["require", .Path, (.Version // ""), "", ""] | @tsv),
          ($manifest.Replace[]? | ["replace", .Old.Path, (.Old.Version // ""), .New.Path, (.New.Version // "")] | @tsv)
      ' >"$cache_file"; then
      return 1
    fi
  fi
  printf '%s\n' "$cache_file"
}

required_version() {
  local module="$1"
  local go_mod_path="$2"
  local index_file
  index_file="$(go_mod_index_file "$go_mod_path")"
  awk -F '\t' -v module="$module" \
    '$1 == "require" && $2 == module { print $3; exit }' "$index_file"
}

required_modules() {
  local go_mod_path="$1"
  local index_file
  index_file="$(go_mod_index_file "$go_mod_path")"
  awk -F '\t' '$1 == "require" { print $2 }' "$index_file"
}

version_specific_local_replacements() {
  local go_mod_path="$1"
  local index_file
  index_file="$(go_mod_index_file "$go_mod_path")"
  awk -F '\t' \
    '$1 == "replace" && $3 != "" && $5 == "" { print $2 "\t" $3 "\t" $4 }' \
    "$index_file"
}

local_replacement_path() {
  local module="$1"
  local go_mod_path="$2"
  local index_file
  index_file="$(go_mod_index_file "$go_mod_path")"
  awk -F '\t' -v module="$module" \
    '$1 == "replace" && $2 == module && $3 == "" && $5 == "" { print $4; exit }' \
    "$index_file"
}

array_contains() {
  local needle="$1"
  shift
  local item
  for item in "$@"; do
    [[ "$item" == "$needle" ]] && return 0
  done
  return 1
}

semver_compare() {
  local left="$1"
  local right="$2"
  local left_without_build="${left%%+*}"
  local right_without_build="${right%%+*}"
  local left_core="${left_without_build#v}"
  local right_core="${right_without_build#v}"
  local left_pre=""
  local right_pre=""
  local -a left_core_parts right_core_parts left_pre_parts right_pre_parts
  local index left_id right_id

  if [[ "$left_core" == *-* ]]; then
    left_pre="${left_core#*-}"
    left_core="${left_core%%-*}"
  fi
  if [[ "$right_core" == *-* ]]; then
    right_pre="${right_core#*-}"
    right_core="${right_core%%-*}"
  fi
  IFS='.' read -r -a left_core_parts <<<"$left_core"
  IFS='.' read -r -a right_core_parts <<<"$right_core"
  if [[ "${#left_core_parts[@]}" -ne 3 ]] || [[ "${#right_core_parts[@]}" -ne 3 ]]; then
    echo "invalid"
    return
  fi
  for index in 0 1 2; do
    left_id="${left_core_parts[$index]}"
    right_id="${right_core_parts[$index]}"
    if [[ ! "$left_id" =~ ^(0|[1-9][0-9]*)$ ]] || [[ ! "$right_id" =~ ^(0|[1-9][0-9]*)$ ]]; then
      echo "invalid"
      return
    fi
    if ((10#$left_id < 10#$right_id)); then echo -1; return; fi
    if ((10#$left_id > 10#$right_id)); then echo 1; return; fi
  done
  if [[ -z "$left_pre" && -z "$right_pre" ]]; then echo 0; return; fi
  if [[ -z "$left_pre" ]]; then echo 1; return; fi
  if [[ -z "$right_pre" ]]; then echo -1; return; fi

  IFS='.' read -r -a left_pre_parts <<<"$left_pre"
  IFS='.' read -r -a right_pre_parts <<<"$right_pre"
  for ((index = 0; index < ${#left_pre_parts[@]} || index < ${#right_pre_parts[@]}; index++)); do
    if ((index >= ${#left_pre_parts[@]})); then echo -1; return; fi
    if ((index >= ${#right_pre_parts[@]})); then echo 1; return; fi
    left_id="${left_pre_parts[$index]}"
    right_id="${right_pre_parts[$index]}"
    if [[ ! "$left_id" =~ ^[0-9A-Za-z-]+$ ]] || [[ ! "$right_id" =~ ^[0-9A-Za-z-]+$ ]]; then
      echo "invalid"
      return
    fi
    if [[ "$left_id" == "$right_id" ]]; then continue; fi
    if [[ "$left_id" =~ ^[0-9]+$ && "$right_id" =~ ^[0-9]+$ ]]; then
      if ((10#$left_id < 10#$right_id)); then echo -1; else echo 1; fi
      return
    fi
    if [[ "$left_id" =~ ^[0-9]+$ ]]; then echo -1; return; fi
    if [[ "$right_id" =~ ^[0-9]+$ ]]; then echo 1; return; fi
    if [[ "$left_id" < "$right_id" ]]; then echo -1; else echo 1; fi
    return
  done
  echo 0
}

for go_mod in "${workspace_go_mods[@]}"; do
  path="$repo_root/$go_mod"
  if [[ ! -f "$path" ]]; then
    echo "::error::$go_mod is missing"
    violations=1
    continue
  fi
  if ! go_mod_index_file "$path" >/dev/null; then
    echo "::error file=$go_mod::Go could not parse the module manifest"
    violations=1
    continue
  fi

  # The workspace models one live source tree for each in-repo module. Its
  # local replacements therefore apply to every selected version. Published
  # version selection uses real module pins and no local path replacement.
  # Reject a version-qualified local replacement instead of approximating Go's
  # MVS semantics and potentially inspecting an inactive source tree.
  while IFS=$'\t' read -r replacement_module replacement_version replacement_path; do
    [[ -n "$replacement_module" ]] || continue
    echo "::error file=$go_mod::version-specific local replacement $replacement_module $replacement_version => $replacement_path is unsupported; workspace-local replacements must be versionless"
    violations=1
  done < <(version_specific_local_replacements "$path")

  # A main module's replace directives apply to matching modules anywhere in
  # its transitive build list, not only to its direct requirements. Walk from
  # the main module's require roots through go.mod requirements, following only
  # replacements owned by this main module. This includes active transitive
  # replacements while leaving unused replacements outside the graph.
  dependency_queue=()
  active_local_go_mods=()
  while IFS= read -r dependency; do
    [[ -n "$dependency" ]] && dependency_queue+=("$dependency")
  done < <(required_modules "$path")
  for ((queue_index = 0; queue_index < ${#dependency_queue[@]}; queue_index++)); do
    dependency="${dependency_queue[$queue_index]}"
    replacement_path="$(local_replacement_path "$dependency" "$path")"
    [[ -n "$replacement_path" ]] || continue
    replacement_root="$(cd "$(dirname "$path")/$replacement_path" 2>/dev/null && pwd -P)" || {
      echo "::error file=$go_mod::active local replacement $replacement_path is missing"
      violations=1
      continue
    }
    if [[ "$replacement_root" != "$repo_root"/* ]]; then
      echo "::error file=$go_mod::active local replacement $replacement_path escapes the repository"
      violations=1
      continue
    fi
    replacement_go_mod="$replacement_root/go.mod"
    if [[ ! -f "$replacement_go_mod" ]]; then
      echo "::error file=$go_mod::active local replacement $replacement_path has no go.mod"
      violations=1
      continue
    fi
    if ! go_mod_index_file "$replacement_go_mod" >/dev/null; then
      echo "::error file=$go_mod::Go could not parse active local replacement $replacement_path/go.mod"
      violations=1
      continue
    fi
    if array_contains "$replacement_go_mod" "${active_local_go_mods[@]-}"; then
      continue
    fi
    active_local_go_mods+=("$replacement_go_mod")
    while IFS= read -r transitive_dependency; do
      [[ -n "$transitive_dependency" ]] && dependency_queue+=("$transitive_dependency")
    done < <(required_modules "$replacement_go_mod")
  done

  for module in "${private_modules[@]}"; do
    recorded_version="$(required_version "$module" "$path")"
    selected_version="$recorded_version"
    selected_from="$go_mod"
    if [[ -n "${active_local_go_mods[*]-}" ]]; then
      for replacement_go_mod in "${active_local_go_mods[@]}"; do
        replacement_version="$(required_version "$module" "$replacement_go_mod")"
        [[ -n "$replacement_version" ]] || continue
        if [[ -z "$selected_version" ]]; then
          selected_version="$replacement_version"
          selected_from="${replacement_go_mod#"$repo_root/"}"
          continue
        fi
        comparison="$(LC_ALL=C semver_compare "$selected_version" "$replacement_version")"
        if [[ "$comparison" == "invalid" ]]; then
          echo "::error file=$go_mod::cannot compare $module versions $selected_version and $replacement_version"
          violations=1
        elif [[ "$comparison" == "-1" ]]; then
          selected_version="$replacement_version"
          selected_from="${replacement_go_mod#"$repo_root/"}"
        fi
      done
    fi

    if [[ -n "$selected_version" && "$recorded_version" != "$selected_version" ]]; then
      echo "::error file=$go_mod::$module standalone graph selects $selected_version from $selected_from, recorded pin is ${recorded_version:-missing}"
      violations=1
    fi
  done
done

exit "$violations"
