name: Update Versions from GitHub

on:
  workflow_dispatch:
  schedule:
    # Runs at 06:00 and 18:00 UTC
    - cron: "0 6,18 * * *"

permissions:
  contents: write
  pull-requests: write

env:
  SOURCES_FILE: frontend/public/json/version-sources.json
  VERSIONS_FILE: frontend/public/json/versions.json

jobs:
  update-versions:
    if: github.repository == 'community-scripts/ProxmoxVE'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
        with:
          ref: main

      - name: Extract version sources from install scripts
        run: |
          set -euo pipefail

          echo "========================================="
          echo "  Extracting version sources from scripts"
          echo "========================================="

          # Initialize sources array
          sources_json="[]"

          # Function to add a source entry
          add_source() {
            local slug="$1"
            local type="$2"
            local source="$3"
            local script="$4"

            # Check if slug already exists (avoid duplicates)
            if echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
              return
            fi

            sources_json=$(echo "$sources_json" | jq \
              --arg slug "$slug" \
              --arg type "$type" \
              --arg source "$source" \
              --arg script "$script" \
              '. += [{"slug": $slug, "type": $type, "source": $source, "script": $script, "version": null, "date": null}]')
          }

          echo ""
          echo "=== Method 1: fetch_and_deploy_gh_release calls ==="
          count=0
          for script in install/*-install.sh; do
            [[ ! -f "$script" ]] && continue
            slug=$(basename "$script" | sed 's/-install\.sh$//')

            # Extract repo from fetch_and_deploy_gh_release "app" "owner/repo"
            while IFS= read -r line; do
              if [[ "$line" =~ fetch_and_deploy_gh_release[[:space:]]+\"[^\"]*\"[[:space:]]+\"([^\"]+)\" ]]; then
                repo="${BASH_REMATCH[1]}"
                add_source "$slug" "github" "$repo" "$script"
                ((count++))
                break  # Only first match per script
              fi
            done < <(grep 'fetch_and_deploy_gh_release' "$script" 2>/dev/null || true)
          done
          echo "Found $count scripts with fetch_and_deploy_gh_release"

          echo ""
          echo "=== Method 2: GitHub URLs in scripts (fallback) ==="
          count=0
          for script in install/*-install.sh; do
            [[ ! -f "$script" ]] && continue
            slug=$(basename "$script" | sed 's/-install\.sh$//')

            # Skip if already found
            if echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
              continue
            fi

            # Look for github.com/owner/repo patterns
            repo=$(grep -oE 'github\.com/([a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+)' "$script" 2>/dev/null \
              | sed 's|github\.com/||' \
              | sed 's/\.git$//' \
              | grep -v 'community-scripts/ProxmoxVE' \
              | grep -v '^repos/' \
              | head -1 || true)

            if [[ -n "$repo" && "$repo" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+$ ]]; then
              add_source "$slug" "github" "$repo" "$script"
              ((count++))
            fi
          done
          echo "Found $count additional scripts with GitHub URLs"

          echo ""
          echo "=== Method 3: npm packages ==="
          # Detect npm install --global <package>
          for script in install/*-install.sh; do
            [[ ! -f "$script" ]] && continue
            slug=$(basename "$script" | sed 's/-install\.sh$//')

            # Skip if already found
            if echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
              continue
            fi

            # Look for npm install --global <package>
            pkg=$(grep -oE 'npm install[^|;]*--global[^|;]*' "$script" 2>/dev/null \
              | grep -oE '\s[a-z][a-z0-9_-]+(@[^\s]+)?$' \
              | tr -d ' ' \
              | sed 's/@.*//' \
              | tail -1 || true)

            if [[ -n "$pkg" ]]; then
              add_source "$slug" "npm" "$pkg" "$script"
            fi
          done

          echo ""
          echo "=== Method 4: Docker images ==="
          # Known Docker-based apps (from docker pull or docker run)
          declare -A docker_mappings=(
            ["homeassistant"]="homeassistant/home-assistant"
            ["portainer"]="portainer/portainer-ce"
            ["dockge"]="louislam/dockge"
            ["immich"]="ghcr.io/immich-app/immich-server"
            ["audiobookshelf"]="ghcr.io/advplyr/audiobookshelf"
            ["podman-homeassistant"]="homeassistant/home-assistant"
          )

          for slug in "${!docker_mappings[@]}"; do
            image="${docker_mappings[$slug]}"
            if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
              add_source "$slug" "docker" "$image" "install/${slug}-install.sh"
            fi
          done

          echo ""
          echo "=== Method 5: Manual GitHub mappings (apt-based apps) ==="
          # Apps that install via apt but have GitHub releases for version tracking
          declare -A manual_github_mappings=(
            ["actualbudget"]="actualbudget/actual"
            ["apache-cassandra"]="apache/cassandra"
            ["apache-couchdb"]="apache/couchdb"
            ["apache-guacamole"]="apache/guacamole-server"
            ["apache-tomcat"]="apache/tomcat"
            ["archivebox"]="ArchiveBox/ArchiveBox"
            ["aria2"]="aria2/aria2"
            ["asterisk"]="asterisk/asterisk"
            ["casaos"]="IceWhaleTech/CasaOS"
            ["checkmk"]="Checkmk/checkmk"
            ["cloudflared"]="cloudflare/cloudflared"
            ["coolify"]="coollabsio/coolify"
            ["crafty-controller"]="crafty-controller/crafty-4"
            ["cross-seed"]="cross-seed/cross-seed"
            ["deconz"]="dresden-elektronik/deconz-rest-plugin"
            ["deluge"]="deluge-torrent/deluge"
            ["dokploy"]="Dokploy/dokploy"
            ["emqx"]="emqx/emqx"
            ["esphome"]="esphome/esphome"
            ["flowiseai"]="FlowiseAI/Flowise"
            ["forgejo"]="forgejo/forgejo"
            ["garage"]="deuxfleurs-org/garage"
            ["ghost"]="TryGhost/Ghost"
            ["grafana"]="grafana/grafana"
            ["graylog"]="Graylog2/graylog2-server"
            ["homebridge"]="homebridge/homebridge"
            ["hyperhdr"]="awawa-dev/HyperHDR"
            ["hyperion"]="hyperion-project/hyperion.ng"
            ["influxdb"]="influxdata/influxdb"
            ["iobroker"]="ioBroker/ioBroker"
            ["jenkins"]="jenkinsci/jenkins"
            ["komodo"]="moghingold/komodo"
            ["lazylibrarian"]="lazylibrarian/LazyLibrarian"
            ["limesurvey"]="LimeSurvey/LimeSurvey"
            ["mariadb"]="MariaDB/server"
            ["mattermost"]="mattermost/mattermost"
            ["meshcentral"]="Ylianst/MeshCentral"
            ["metabase"]="metabase/metabase"
            ["mongodb"]="mongodb/mongo"
            ["mysql"]="mysql/mysql-server"
            ["neo4j"]="neo4j/neo4j"
            ["node-red"]="node-red/node-red"
            ["ntfy"]="binwiederhier/ntfy"
            ["nzbget"]="nzbgetcom/nzbget"
            ["octoprint"]="OctoPrint/OctoPrint"
            ["onedev"]="theonedev/onedev"
            ["onlyoffice"]="ONLYOFFICE/DocumentServer"
            ["openhab"]="openhab/openhab-distro"
            ["openobserve"]="openobserve/openobserve"
            ["openwebui"]="open-webui/open-webui"
            ["passbolt"]="passbolt/passbolt_api"
            ["pihole"]="pi-hole/pi-hole"
            ["postgresql"]="postgres/postgres"
            ["rabbitmq"]="rabbitmq/rabbitmq-server"
            ["readarr"]="Readarr/Readarr"
            ["redis"]="redis/redis"
            ["runtipi"]="runtipi/runtipi"
            ["sftpgo"]="drakkan/sftpgo"
            ["shinobi"]="ShinobiCCTV/Shinobi"
            ["sonarqube"]="SonarSource/sonarqube"
            ["sonarr"]="Sonarr/Sonarr"
            ["syncthing"]="syncthing/syncthing"
            ["tdarr"]="HaveAGitGat/Tdarr"
            ["technitiumdns"]="TechnitiumSoftware/DnsServer"
            ["transmission"]="transmission/transmission"
            ["typesense"]="typesense/typesense"
            ["unmanic"]="Unmanic/unmanic"
            ["valkey"]="valkey-io/valkey"
            ["verdaccio"]="verdaccio/verdaccio"
            ["vikunja"]="go-vikunja/vikunja"
            ["wazuh"]="wazuh/wazuh"
            ["wordpress"]="WordPress/WordPress"
            ["zabbix"]="zabbix/zabbix"
            ["zammad"]="zammad/zammad"
            ["zerotier-one"]="zerotier/ZeroTierOne"
            # Apps without known GitHub repos (use "-" as placeholder)
            ["agentdvr"]="-"
            ["apt-cacher-ng"]="-"
            ["channels"]="-"
            ["daemonsync"]="-"
            ["dotnetaspwebapi"]="-"
            ["fhem"]="-"
            ["fileflows"]="-"
            ["fumadocs"]="-"
            ["infisical"]="-"
            ["itsm-ng"]="-"
            ["jupyternotebook"]="-"
            ["kasm"]="-"
            ["lyrionmusicserver"]="-"
            ["minarca"]="-"
            ["mqtt"]="-"
            ["nextcloudpi"]="-"
            ["nextpvr"]="-"
            ["notifiarr"]="-"
            ["nxwitness"]="-"
            ["omada"]="-"
            ["omv"]="-"
            ["plex"]="-"
            ["podman"]="-"
            ["readeck"]="-"
            ["resiliosync"]="-"
            ["smokeping"]="-"
            ["splunk-enterprise"]="-"
            ["sqlserver2022"]="-"
            ["swizzin"]="-"
            ["teamspeak-server"]="-"
            ["twingate-connector"]="-"
            ["unifi"]="-"
            ["urbackupserver"]="-"
            ["yunohost"]="-"
          )

          for slug in "${!manual_github_mappings[@]}"; do
            repo="${manual_github_mappings[$slug]}"
            if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
              # Skip placeholder entries in extraction, they get added in Method 8
              [[ "$repo" == "-" ]] && continue
              add_source "$slug" "github" "$repo" "install/${slug}-install.sh"
            fi
          done

          echo ""
          echo "=== Method 6: Proxmox LXC templates ==="
          # Base OS versions from Proxmox template index
          declare -A pveam_mappings=(
            ["debian"]="pveam:debian"
            ["ubuntu"]="pveam:ubuntu"
            ["alpine"]="pveam:alpine"
          )

          for slug in "${!pveam_mappings[@]}"; do
            template="${pveam_mappings[$slug]}"
            if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
              add_source "$slug" "pveam" "$template" "ct/${slug}.sh"
            fi
          done

          echo ""
          echo "=== Method 7: Special sources ==="
          # Home Assistant OS VM
          if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"haos-vm\")" > /dev/null 2>&1; then
            add_source "haos-vm" "github" "home-assistant/operating-system" "vm/haos-vm.sh"
          fi

          echo ""
          echo "=== Method 8: Unknown/Manual apps ==="
          # Apps without known version sources - add with type "manual" for manual updates
          unknown_apps=(
            "agentdvr" "apt-cacher-ng" "channels" "daemonsync" "dotnetaspwebapi"
            "fhem" "fileflows" "fumadocs" "infisical" "itsm-ng" "jupyternotebook"
            "kasm" "lyrionmusicserver" "minarca" "mqtt" "nextcloudpi" "nextpvr"
            "notifiarr" "nxwitness" "omada" "omv" "plex" "podman" "readeck"
            "resiliosync" "smokeping" "splunk-enterprise" "sqlserver2022" "swizzin"
            "teamspeak-server" "twingate-connector" "unifi" "urbackupserver" "yunohost"
          )

          for slug in "${unknown_apps[@]}"; do
            if ! echo "$sources_json" | jq -e ".[] | select(.slug == \"$slug\")" > /dev/null 2>&1; then
              add_source "$slug" "manual" "-" "install/${slug}-install.sh"
            fi
          done

          # Save sources file
          echo "$sources_json" | jq --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
            '{generated: $date, sources: (. | sort_by(.slug))}' > "$SOURCES_FILE"

          total=$(echo "$sources_json" | jq 'length')
          echo ""
          echo "========================================="
          echo "  Total sources extracted: $total"
          echo "========================================="

      - name: Fetch versions for all sources
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -euo pipefail

          echo "========================================="
          echo "  Fetching versions from sources"
          echo "========================================="

          success=0
          failed=0
          manual=0
          total=$(jq '.sources | length' "$SOURCES_FILE")

          # Process each source
          for i in $(seq 0 $((total - 1))); do
            entry=$(jq -r ".sources[$i]" "$SOURCES_FILE")
            slug=$(echo "$entry" | jq -r '.slug')
            type=$(echo "$entry" | jq -r '.type')
            source=$(echo "$entry" | jq -r '.source')

            echo -n "[$((i+1))/$total] $slug ($type: $source) ... "

            version=""
            date=""

            case "$type" in
              github)
                # Try releases first
                response=$(gh api "repos/${source}/releases/latest" 2>/dev/null || echo '{"message": "Not Found"}')

                if echo "$response" | jq -e '.tag_name' > /dev/null 2>&1; then
                  version=$(echo "$response" | jq -r '.tag_name')
                  date=$(echo "$response" | jq -r '.published_at // empty')
                else
                  # Fallback to tags
                  version=$(gh api "repos/${source}/tags" --jq '.[0].name // empty' 2>/dev/null || echo "")
                fi
                ;;

              npm)
                response=$(curl -fsSL "https://registry.npmjs.org/${source}/latest" 2>/dev/null || echo '{}')
                version=$(echo "$response" | jq -r '.version // empty')
                ;;

              docker)
                if [[ "$source" == ghcr.io/* ]]; then
                  # GitHub Container Registry
                  ghcr_path="${source#ghcr.io/}"
                  owner="${ghcr_path%%/*}"
                  pkg="${ghcr_path##*/}"
                  version=$(gh api "users/${owner}/packages/container/${pkg}/versions" --jq '.[0].metadata.container.tags[] | select(. != "latest")' 2>/dev/null | head -1 || echo "")
                else
                  # Docker Hub
                  version=$(curl -fsSL "https://hub.docker.com/v2/repositories/${source}/tags?page_size=10&ordering=last_updated" 2>/dev/null \
                    | jq -r '.results[] | select(.name != "latest") | .name' | head -1 || echo "")
                fi
                ;;

              pveam)
                # Proxmox LXC template versions from download.proxmox.com
                os_name="${source#pveam:}"
                # Fetch the template index and get latest version
                version=$(curl -fsSL "http://download.proxmox.com/images/system/" 2>/dev/null \
                  | grep -oE "${os_name}-[0-9]+\.[0-9]+-default_[0-9]+_amd64" \
                  | sed "s/${os_name}-//" | sed 's/-default.*//' \
                  | sort -V | tail -1 || echo "")
                ;;

              manual)
                # Manual entries - no automatic version fetching
                # These need to be updated manually or have their source type changed
                version="-"
                ((manual++))
                echo -n "(manual) "
                ;;
            esac

            if [[ -n "$version" && "$version" != "null" ]]; then
              # Update the source entry with version
              jq --arg idx "$i" --arg version "$version" --arg date "${date:-}" \
                '.sources[$idx | tonumber].version = $version | .sources[$idx | tonumber].date = $date' \
                "$SOURCES_FILE" > "${SOURCES_FILE}.tmp" && mv "${SOURCES_FILE}.tmp" "$SOURCES_FILE"
              echo "✓ $version"
              ((success++))
            else
              echo "⚠ no version found"
              ((failed++))
            fi
          done

          echo ""
          echo "========================================="
          echo "           SUMMARY"
          echo "========================================="
          echo "Success: $success (automated)"
          echo "Manual:  $manual (placeholder)"
          echo "Failed:  $failed"
          echo "Total:   $total"
          echo "========================================="

      - name: Generate versions.json for compatibility
        run: |
          # Convert version-sources.json to versions.json format for backward compatibility
          jq '[.sources[] | select(.version != null) | {name: .source, version: .version, date: .date}]' \
            "$SOURCES_FILE" > "$VERSIONS_FILE"

          echo "Generated versions.json with $(jq length "$VERSIONS_FILE") entries"

      - name: Check for changes
        id: check-changes
        run: |
          if git diff --quiet "$SOURCES_FILE" "$VERSIONS_FILE" 2>/dev/null; then
            echo "changed=false" >> "$GITHUB_OUTPUT"
            echo "No changes detected"
          else
            echo "changed=true" >> "$GITHUB_OUTPUT"
            echo "Changes detected:"
            git diff --stat "$SOURCES_FILE" "$VERSIONS_FILE" 2>/dev/null || true
          fi

      - name: Create Pull Request
        if: steps.check-changes.outputs.changed == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          BRANCH_NAME="automated/update-versions-$(date +%Y%m%d)"

          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "GitHub Actions[bot]"

          # Check if branch exists and delete it
          git push origin --delete "$BRANCH_NAME" 2>/dev/null || true

          git checkout -b "$BRANCH_NAME"
          git add "$SOURCES_FILE" "$VERSIONS_FILE"
          git commit -m "chore: update version-sources.json and versions.json

          Sources: $(jq '.sources | length' "$SOURCES_FILE")
          With versions: $(jq '[.sources[] | select(.version != null)] | length' "$SOURCES_FILE")
          Generated: $(jq -r '.generated' "$SOURCES_FILE")"

          git push origin "$BRANCH_NAME" --force

          # Check if PR already exists
          existing_pr=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number // empty')

          if [[ -n "$existing_pr" ]]; then
            echo "PR #$existing_pr already exists, updating..."
          else
            gh pr create \
              --title "[Automated] Update version-sources.json" \
              --body "This PR updates version information from multiple sources.

          ## Sources
          - **GitHub Releases**: Direct from \`fetch_and_deploy_gh_release\` calls
          - **GitHub URLs**: Extracted from install scripts
          - **npm Registry**: For Node.js based apps
          - **Docker Hub/GHCR**: For container-based apps

          ## Stats
          - Total sources: $(jq '.sources | length' "$SOURCES_FILE")
          - With versions: $(jq '[.sources[] | select(.version != null)] | length' "$SOURCES_FILE")

          ---
          *Automatically generated from install scripts*" \
              --base main \
              --head "$BRANCH_NAME" \
              --label "automated pr"
          fi

      - name: Auto-approve PR
        if: steps.check-changes.outputs.changed == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          BRANCH_NAME="automated/update-versions-$(date +%Y%m%d)"
          pr_number=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number')
          if [[ -n "$pr_number" ]]; then
            gh pr review "$pr_number" --approve
          fi
