#!/usr/bin/env bash


###
#
# Options Section
#
###

set -e
set -u
set -o pipefail


###
#
# Variable Section
#
###

REPOSITORY_ARCHITECTURES='arm64|amd64'
REPOSITORY_CODENAMES='bookworm|trixie|noble'
REPOSITORY_COMPONENTS='main|pve8|pve8-ceph|pve9|pve9-ceph|pbs3|pbs4'
REPOSITORY_DIRECTORY_DIST="$(/usr/bin/grep 'distdir' '/etc/reprepro/options' | /usr/bin/sed --expression='s/distdir //')"
REPOSITORY_DIRECTORY_INCLUDE="$(/usr/bin/grep 'outdir' '/etc/reprepro/options' | /usr/bin/sed --expression='s/outdir //')/include"
PLUGIN_ARM64_PBS3_URL='https://api.github.com/repos/wofferl/proxmox-backup-arm64/releases/tags/3.4.8-3'
PLUGIN_ARM64_PBS4_URL='https://api.github.com/repos/wofferl/proxmox-backup-arm64/releases/tags/4.2.0-1'
PLUGIN_ARM64_PVE8_URL='https://mirrors.lierfang.com/pxcloud/pxvirt/dists/bookworm/main/binary-arm64/'
PLUGIN_ARM64_PVE8_CEPH_URL='https://mirrors.lierfang.com/pxcloud/pxvirt/dists/bookworm/ceph-squid/binary-arm64/'
PLUGIN_ARM64_PVE9_URL='https://mirrors.lierfang.com/pxcloud/pxvirt/dists/trixie/main/binary-arm64/'
PLUGIN_ARM64_PVE9_CEPH_URL='https://mirrors.lierfang.com/pxcloud/pxvirt/dists/trixie/ceph-squid/binary-arm64/'


###
#
# Function Section
#
###

function error () {
  if [[ -n "${1:-}" ]]; then
    /usr/bin/echo -n -e "${1}"
  fi
  if [[ -n "${2:-}" ]]; then
    /usr/bin/echo -n -e "${2}"
  fi
  exit 1
}

function help () {
  local HELP
  set +e
  case "${1}" in
    general)
      read -r -d '' HELP << EOL
Usage: repository (Option) (Package / Script)

Options:
  [ -h ]            |  Help
  [ -i ]            |  Include Package
  [ -l ]            |  List Repository Packages
  [ -p ]            |  Repository Plugin's
  [ -r ]            |  Remove Package
EOL
    ;;
    include)
      read -r -d '' HELP << EOL
Usage: repository -i (Package) (Codename) (Component)

Description:
  Add a package specifying the repository codename and component.

Codename's:
  - bookworm
  - trixie
  - noble

Component's:
  - main
  - pve8
  - pve8-ceph
  - pve9
  - pve9-ceph
  - pbs3
  - pbs4
EOL
    ;;
    list)
      read -r -d '' HELP << EOL
Usage: repository -l

Description:

EOL
    ;;
    plugin)
      read -r -d '' HELP << EOL
Usage: repository -p (Plugin)

Description:
  Initiate the execution of a specific plugin listed below ...

Plugin's:
  - automatic  - Import all Packages from the inlcude directory in the repository.

  - arm64_pbs3 - Provides a Proxmox Backup Server 3 ARM64 repository.
  - arm64_pbs4 - Provides a Proxmox Backup Server 4 ARM64 repository.
  - arm64_pve8 - Provides a Proxmox Virtual Environment 8 ARM64 repository.
  - arm64_pve9 - Provides a Proxmox Virtual Environment 9 ARM64 repository.
EOL
    ;;
    remove)
      read -r -d '' HELP << EOL
Usage: repository -r (Package) (Codename) (Component) (Architecture)

Description:
  Removes a package from the repository by specifying the codename, component, and architecture.
  If codename, component, and architecture are not specified, all packages in the repository are removed.

Codename's:
  - bookworm
  - trixie
  - noble

Component's:
  - main
  - pve8
  - pve8-ceph
  - pve9
  - pve9-ceph
  - pbs3
  - pbs4

Architecture's:
  - amd64
  - arm64
EOL
    ;;
  esac
  /usr/bin/echo -e "${HELP}"
}

function input_architecture () {
  local INPUT="${1}"
  if [[ ! "${INPUT}" =~ (${REPOSITORY_ARCHITECTURES}) ]]; then
    /usr/bin/echo '1'
  fi
}

function input_codename () {
  local INPUT="${1}"
  if [[ ! "${INPUT}" =~ (${REPOSITORY_CODENAMES}) ]]; then
    /usr/bin/echo '1'
  fi
}

function input_component () {
  local INPUT="${1}"
  if [[ ! "${INPUT}" =~ (${REPOSITORY_COMPONENTS}) ]]; then
    /usr/bin/echo '1'
  fi
}

function input_deb () {
  local INPUT="${1}"
  if ! /usr/bin/dpkg-deb --info "${INPUT}" &> '/dev/null'; then
    /usr/bin/echo '1'
  fi
}

function input_package () {
  local INPUT_PACKAGE="${1}"
  local INPUT_COMPONENT="${2}"
  local INPUT_CODENAME="${3}"
  local INPUT_ARCHITECTURE="${4}"
  if ! /usr/bin/grep --quiet --word-regexp "Package: ${INPUT_PACKAGE}" "${REPOSITORY_DIRECTORY_DIST}/${INPUT_CODENAME}/${INPUT_COMPONENT}/binary-${INPUT_ARCHITECTURE}/Packages"; then
    /usr/bin/echo '1'
  fi
}

function output () {
  if [[ -n "${1:-}" ]]; then
    /usr/bin/echo -n -e "${1}"
  fi
}

function plugin_arm64_pbs_import () {
  local REPOSITORY="${1}"
  local CODENAME="${2}"
  local COMPONENT="${3}"
  local TMP
  TMP=$(
    /usr/bin/mktemp --directory \
    --quiet
  )
  trap "/usr/bin/rm --force --recursive ${TMP}" EXIT
  /usr/bin/wget --quiet \
                --output-document="${TMP}/gitapi.json" \
                "${REPOSITORY}"
  local URLS
  URLS=$(
    /usr/bin/grep 'browser_download_url' \
                  "${TMP}/gitapi.json" | \
    /usr/bin/cut --delimiter='"' \
                 --fields='4'
  )
  /usr/bin/sleep '3s'
  local URL
  for URL in ${URLS}; do
    local PACKAGE
    PACKAGE=$(
      /usr/bin/wget --quiet \
                    --output-document='-' \
                    "${URL}" | \
      /usr/bin/dpkg-deb --info '/dev/stdin' | \
      /usr/bin/grep ' Package:' | \
      /usr/bin/sed --expression='s/ Package: //' || \
        /usr/bin/true
    )
    local VERSION_ONLINE
    VERSION_ONLINE=$(
      /usr/bin/wget --quiet \
                    --output-document='-' \
                    "${URL}" | \
      /usr/bin/dpkg-deb --info '/dev/stdin' | \
      /usr/bin/grep ' Version:' | \
      /usr/bin/sed --expression='s/ Version: //' || \
        /usr/bin/true
    )
    local VERSION_OFFLINE
    VERSION_OFFLINE=$(
      /usr/bin/reprepro --confdir '/etc/reprepro' \
                        --list-format '${version}' \
                        --component "${COMPONENT}" \
                        --architecture 'arm64' \
                        list "${CODENAME}" \
                        "${PACKAGE}"
    )
    if /usr/bin/dpkg --compare-versions "${VERSION_ONLINE}" gt "${VERSION_OFFLINE}" 2>/dev/null; then
      local DEB
      DEB=$(/usr/bin/basename "${URL}")
      /usr/bin/wget --quiet \
                    --output-document="${TMP}/${DEB}" \
                    "${URL}"
      /usr/bin/reprepro --confdir '/etc/reprepro' \
                        --component "${COMPONENT}" \
                        --architecture 'arm64' \
                        includedeb "${CODENAME}" \
                        "${TMP}/${DEB}"
      /usr/bin/rm --force "${TMP}/${DEB}"
    fi
    unset PACKAGE
    unset VERSION_ONLINE
    unset VERSION_OFFLINE
  done
}

function plugin_arm64_pve_import () {
  local REPOSITORY="${1}"
  local CODENAME="${2}"
  local COMPONENT="${3}"
  local TMP
  TMP=$(
    /usr/bin/mktemp --directory \
    --quiet
  )
  trap "/usr/bin/rm --force --recursive ${TMP}" EXIT
  /usr/bin/wget --quiet \
                --output-document="${TMP}/repository.html" \
                "${REPOSITORY}"
  local PACKAGES
  PACKAGES=$(
    /usr/bin/grep --perl-regexp \
                  --only-matching 'href="\K[^"]*(arm64|all)[^"]*\.deb' \
                  "${TMP}/repository.html"
  )
  /usr/bin/sleep '3s'
  local DEB
  for DEB in ${PACKAGES}; do
    local PACKAGE
    PACKAGE=$(
      /usr/bin/wget --quiet \
                    --output-document='-' \
                    "${REPOSITORY}${DEB}" | \
      /usr/bin/dpkg-deb --info '/dev/stdin' | \
      /usr/bin/grep ' Package:' | \
      /usr/bin/sed --expression='s/ Package: //' || \
        /usr/bin/true
    )
    local VERSION_ONLINE
    VERSION_ONLINE=$(
      /usr/bin/wget --quiet \
                    --output-document='-' \
                    "${REPOSITORY}${DEB}" | \
      /usr/bin/dpkg-deb --info '/dev/stdin' | \
      /usr/bin/grep ' Version:' | \
      /usr/bin/sed --expression='s/ Version: //' || \
        /usr/bin/true
    )
    local VERSION_OFFLINE
    VERSION_OFFLINE=$(
      /usr/bin/reprepro --confdir '/etc/reprepro' \
                        --list-format '${version}' \
                        --component "${COMPONENT}" \
                        --architecture 'arm64' \
                        list "${CODENAME}" \
                        "${PACKAGE}"
    )
    if /usr/bin/dpkg --compare-versions "${VERSION_ONLINE}" gt "${VERSION_OFFLINE}" 2>/dev/null; then
      /usr/bin/wget --quiet \
                    --output-document="${TMP}/${DEB}" \
                    "${REPOSITORY}${DEB}"
      /usr/bin/reprepro --confdir '/etc/reprepro' \
                        --component "${COMPONENT}" \
                        --architecture 'arm64' \
                        includedeb "${CODENAME}" \
                        "${TMP}/${DEB}"
      /usr/bin/rm --force "${TMP}/${DEB}"
    fi
    unset PACKAGE
    unset VERSION_ONLINE
    unset VERSION_OFFLINE
  done
}

function plugin_automatic_import () {
  local DIRECTORY="${1}"
  for CONFIG in "${DIRECTORY}/"*'.cfg'; do
    source "${CONFIG}"
    DEB="${CONFIG%.cfg}.deb"
    if ! /usr/bin/dpkg-deb --info "${DEB}" &> '/dev/null'; then
      /usr/bin/rm "${DEB}" || \
        exit 1
      continue
    fi
    variable_architecture "${DEB}"
    variable_hash "${DEB}"
    variable_package "${DEB}"
    variable_version "${DEB}"
    IFS='|'
    for CODENAME in ${REPOSITORY_CODENAMES}; do
      for COMPONENT in ${REPOSITORY_COMPONENTS}; do
        VERSION_REPOSITORY=$(
          /usr/bin/reprepro --confdir '/etc/reprepro' \
                            --list-format '${version}' \
                            --component "${COMPONENT}" \
                            --architecture "${ARCHITECTURE}" \
                            list "${CODENAME}" \
                            "${PACKAGE}"
        )
        HASH_REPOSITORY=$(
          /usr/bin/reprepro --confdir '/etc/reprepro' \
                            --list-format '${sha256}' \
                            --component "${COMPONENT}" \
                            --architecture "${ARCHITECTURE}" \
                            list "${CODENAME}" \
                            "${PACKAGE}"
        )
        if /usr/bin/dpkg --compare-versions "${VERSION}" le "${VERSION_REPOSITORY}" && \
           [[ "${HASH}" != "${HASH_REPOSITORY}" ]]; then
          /usr/bin/reprepro --confdir '/etc/reprepro' \
                            --component "${COMPONENT}" \
                            --architecture "${ARCHITECTURE}" \
                            remove "${CODENAME}" \
                            "${PACKAGE}" &> '/dev/null'
        fi
      done
    done
    unset IFS
    for CODENAME in ${REPOSITORY_CODENAME}; do
      for COMPONENT in ${REPOSITORY_COMPONENT}; do
        if /usr/bin/reprepro --confdir '/etc/reprepro' \
                          --component "${COMPONENT}" \
                          --architecture "${ARCHITECTURE}" \
                          includedeb "${CODENAME}" \
                          "${DEB}" &> '/dev/null'; then
          local STATUS='0'
        else
          local STATUS='1'
          continue
        fi
      done
    done
    if [[ "${STATUS}" -eq '0' ]]; then
      /usr/bin/rm --force "${CONFIG}"
      /usr/bin/rm --force "${DEB}"
    fi
  done
}

function variable_architecture () {
  local DEB="${1}"
  ARCHITECTURE=$(
    /usr/bin/dpkg-deb --info "${DEB}" | \
    /usr/bin/grep ' Architecture:' | \
    /usr/bin/sed --expression='s/ Architecture: //'
  )
}

function variable_hash () {
  local DEB="${1}"
  HASH=$(
    /usr/bin/sha256sum "${DEB}" | \
    /usr/bin/awk '{ print $1 }'
  )
}

function variable_package () {
  local DEB="${1}"
  PACKAGE=$(
    /usr/bin/dpkg-deb --info "${DEB}" | \
    /usr/bin/grep ' Package:' | \
    /usr/bin/sed --expression='s/ Package: //'
  )
}

function variable_version () {
  local DEB="${1}"
  VERSION=$(
    /usr/bin/dpkg-deb --info "${DEB}" | \
    /usr/bin/grep ' Version:' | \
    /usr/bin/sed --expression='s/ Version: //'
  )
}


###
#
# Runtime Environment
#
###

while getopts ':hi:lp:r:' OPT; do
  case "${OPT}" in
    h)
      help 'general'
      exit 0
    ;;
    i)
      output 'Reprepro Repository\n\n'
      DEB="${OPTARG}"
      CODENAME="${3:-}"
      COMPONENT="${4:-}"
      if [[ $(input_deb "${DEB}") -ne '0' ]] || \
         [[ $(input_codename "${CODENAME}") -ne '0' ]] || \
         [[ $(input_component "${COMPONENT}") -ne '0' ]]; then
        help 'include'
        exit 1
      fi
      variable_architecture "${DEB}"
      variable_hash "${DEB}"
      variable_package "${DEB}"
      variable_version "${DEB}"
      VERSION_REPOSITORY=$(
        /usr/bin/reprepro --confdir '/etc/reprepro' \
                          --list-format '${version}' \
                          --component "${COMPONENT}" \
                          --architecture "${ARCHITECTURE}" \
                          list "${CODENAME}" \
                          "${PACKAGE}"
      )
      HASH_REPOSITORY=$(
        /usr/bin/reprepro --confdir '/etc/reprepro' \
                          --list-format '${sha256}' \
                          --component "${COMPONENT}" \
                          --architecture "${ARCHITECTURE}" \
                          list "${CODENAME}" \
                          "${PACKAGE}"
      )
      if /usr/bin/dpkg --compare-versions "${VERSION}" le "${VERSION_REPOSITORY}" 2>/dev/null && \
         [[ "${HASH}" != "${HASH_REPOSITORY}" ]]; then
        read -r -p "Confirm to replace package '${PACKAGE}:${VERSION}' (Yes/No): " CONFIRM
        if [[ "${CONFIRM}" =~ ^(y|Y|yes|YES)$ ]]; then
          output 'Remove package from repository ...'
          IFS='|'
          for CODENAME in ${REPOSITORY_CODENAMES}; do
            /usr/bin/reprepro --confdir '/etc/reprepro' \
                              --component "${COMPONENT}" \
                              --architecture "${ARCHITECTURE}" \
                              remove "${CODENAME}" \
                              "${PACKAGE}" &> '/dev/null' || \
              error '\r\rRemove package from repository [ERROR]\n' \
                    "  => The package '${PACKAGE}:${ARCHITECTURE}' could not be removed from repository '${CODENAME}'.\n"
          done
          unset IFS
          output '\r\rRemove package from repository [OK]\n'
        fi
      fi
      output "Add package '${PACKAGE}:${ARCHITECTURE}' to repository '${COMPONENT}' ..."
      /usr/bin/reprepro --confdir '/etc/reprepro' \
                        --component "${COMPONENT}" \
                        --architecture "${ARCHITECTURE}" \
                        includedeb "${CODENAME}" \
                        "${DEB}" &> '/dev/null' || \
        error "\r\rAdd package '${PACKAGE}:${ARCHITECTURE}' to repository '${COMPONENT}' [ERROR]\n"
      output "\r\rAdd package '${PACKAGE}:${ARCHITECTURE}' to repository '${COMPONENT}' [OK]\n"
      exit 0
    ;;
    l)
      output 'Reprepro Repository (Database)\n\n'
      mapfile -t DATABASE < <(
        IFS='|'
        for CODENAME in ${REPOSITORY_CODENAMES}; do
          for COMPONENT in ${REPOSITORY_COMPONENTS}; do
            /usr/bin/reprepro --confdir '/etc/reprepro' \
                              --list-format '${$codename} ${$component} ${$architecture} ${package} ${version}\n' \
                              --component "${COMPONENT}" \
                              list "${CODENAME}" | \
            /usr/bin/sed --expression='s/^\([^|]*\)|\1|//'
          done
        done
        unset IFS
      )
      /usr/bin/printf ' '
      /usr/bin/printf '%.0s-' {1..149}
      /usr/bin/printf "\n| %-50s | %-25s | %-20s | %-20s | %-20s |%s\n" "Package" "Version" "Component" "Codename" "Architecture"
      /usr/bin/printf ' '
      /usr/bin/printf "%.0s-" {1..149}
      /usr/bin/printf "\n"
      for ENTRY in "${DATABASE[@]}"; do
        CODENAME_OLD="${CODENAME:-}"
        CODENAME=$(/usr/bin/awk '{print $1}' <<< "${ENTRY}")
        COMPONENT_OLD="${COMPONENT:-}"
        COMPONENT=$(/usr/bin/awk '{print $2}' <<< "${ENTRY}")
        ARCHITECTURE_OLD="${ARCHITECTURE:-}"
        ARCHITECTURE=$(/usr/bin/awk '{print $3}' <<< "${ENTRY}")
        PACKAGE=$(/usr/bin/awk '{print $4}' <<< "${ENTRY}")
        VERSION=$(/usr/bin/awk '{print $5}' <<< "${ENTRY}")
        if [[ "${CODENAME}" != "${CODENAME_OLD:=${CODENAME}}" ]]; then
          /usr/bin/printf ' '
          /usr/bin/printf "%.0s-" {1..149}
          /usr/bin/printf "\n"
        elif [[ "${COMPONENT}" != "${COMPONENT_OLD:=${COMPONENT}}" ]]; then
          /usr/bin/printf ' '
          /usr/bin/printf "%.0s-" {1..149}
          /usr/bin/printf "\n"
        elif [[ "${ARCHITECTURE}" != "${ARCHITECTURE_OLD:=${ARCHITECTURE}}" ]]; then
          /usr/bin/printf ' '
          /usr/bin/printf "%.0s-" {1..149}
          /usr/bin/printf "\n"
        fi
        /usr/bin/printf "| %-50s | %-25s | %-20s | %-20s | %-20s |%s\n" \
                        "${PACKAGE}" \
                        "${VERSION}" \
                        "${COMPONENT}" \
                        "${CODENAME}" \
                        "${ARCHITECTURE}"
      done
      /usr/bin/printf ' '
      /usr/bin/printf "%.0s-" {1..149}
      /usr/bin/printf "\n"
      exit 0
    ;;
    p)
      PLUGIN="${OPTARG}"
      case "${PLUGIN}" in
        arm64_pbs3)
          plugin_arm64_pbs_import "${PLUGIN_ARM64_PBS3_URL}" 'bookworm' 'pbs3'
        ;;
        arm64_pbs4)
          plugin_arm64_pbs_import "${PLUGIN_ARM64_PBS4_URL}" 'trixie' 'pbs4'
        ;;
        arm64_pve8)
          plugin_arm64_pve_import "${PLUGIN_ARM64_PVE8_URL}" 'bookworm' 'pve8'
          plugin_arm64_pve_import "${PLUGIN_ARM64_PVE8_CEPH_URL}" 'bookworm' 'pve8-ceph'
        ;;
        arm64_pve9)
          plugin_arm64_pve_import "${PLUGIN_ARM64_PVE9_URL}" 'trixie' 'pve9'
          plugin_arm64_pve_import "${PLUGIN_ARM64_PVE9_CEPH_URL}" 'trixie' 'pve9-ceph'
        ;;
        automatic)
          plugin_automatic_import "${REPOSITORY_DIRECTORY_INCLUDE}"
        ;;
        *)
          exit 1
        ;;
      esac
      exit 0
    ;;
    r)
      output 'Reprepro Repository\n\n'
      PACKAGE="${OPTARG}"
      CODENAME="${3:-}"
      COMPONENT="${4:-}"
      ARCHITECTURE="${5:-}"
      if [[ -z "${CODENAME}" && \
            -z "${COMPONENT}" && \
            -z "${ARCHITECTURE}" ]]; then
        output 'Remove package from repository ...'
        IFS='|'
        for CODENAME in ${REPOSITORY_CODENAMES}; do
          for COMPONENT in ${REPOSITORY_COMPONENTS}; do
            if /usr/bin/reprepro --confdir '/etc/reprepro' \
                                 --component "${COMPONENT}" \
                                 list "${CODENAME}" \
                                 "${PACKAGE}" &> '/dev/null'; then
              /usr/bin/reprepro --confdir '/etc/reprepro' \
                                --component "${COMPONENT}" \
                                remove "${CODENAME}" \
                                "${PACKAGE}" &> '/dev/null' || \
                error '\r\rRemove package from repository [ERROR]\n' \
                      "  => The package '${PACKAGE}' could not be removed from '${COMPONENT}' in repository '${CODENAME}'.\n"
            else
              continue
            fi
          done
        done
        unset IFS
        output '\r\rRemove package from repository [OK]\n'
      elif [[ -n "${CODENAME}" || \
              -n "${COMPONENT}" || \
              -n "${ARCHITECTURE}" ]]; then
        if [[ $(input_codename "${CODENAME}") -ne '0' ]] || \
           [[ $(input_component "${COMPONENT}") -ne '0' ]] || \
           [[ $(input_architecture "${ARCHITECTURE}") -ne '0' ]] || \
           [[ $(input_package "${PACKAGE}" "${COMPONENT}" "${CODENAME}" "${ARCHITECTURE}") -ne '0' ]]; then
          help 'remove'
          exit 1
        fi
        output 'Remove package from repository ...'
        /usr/bin/reprepro --confdir '/etc/reprepro' \
                          --component "${COMPONENT}" \
                          --architecture "${ARCHITECTURE}" \
                          remove "${CODENAME}" \
                          "${PACKAGE}" &> '/dev/null' || \
          error '\r\rRemove package from repository [ERROR]\n' \
                "  => The package '${PACKAGE}:${ARCHITECTURE}' could not be removed from '${COMPONENT}' in repository '${CODENAME}'.\n"
        output '\r\rRemove package from repository [OK]\n'
      fi
      exit 0
    ;;
    -*|*)
      case "${1}" in
        -i)
          help 'include'
          exit 1
        ;;
        -l)
          help 'list'
          exit 1
        ;;
        -p)
          help 'plugin'
          exit 1
        ;;
        -r)
          help 'remove'
          exit 1
        ;;
      esac
  esac
done
help 'general'
shift $((OPTIND-1))
