name: CI

on:
  push:
    branches: [main, 'feature/**', 'fix/**', 'feat/**']
    tags-ignore:
      - 'v*'
    paths-ignore:
      - '.planning/**'
      - 'docs/**'
      - '**/*.md'
      - '.claude/**'
      - 'AGENTS.md'
      - 'CLAUDE.md'
  pull_request:
    branches: [main]
    paths-ignore:
      - '.planning/**'
      - 'docs/**'
      - '**/*.md'
      - '.claude/**'
  delete:

jobs:
  build:
    runs-on: ubuntu-latest
    if: github.event_name != 'delete'
    container:
      image: gitea.siegeln.net/cameleer/cameleer-build:1
      credentials:
        username: cameleer
        password: ${{ secrets.REGISTRY_TOKEN }}
    steps:
      - uses: actions/checkout@v4

      - name: Configure Gitea Maven Registry
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << 'SETTINGS'
          <settings>
            <servers>
              <server>
                <id>gitea</id>
                <username>cameleer</username>
                <password>${env.REGISTRY_TOKEN}</password>
              </server>
            </servers>
          </settings>
          SETTINGS
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}

      - name: Cache Maven dependencies
        uses: actions/cache@v4
        with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
          restore-keys: ${{ runner.os }}-maven-

      - name: Cache npm registry
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('ui/package-lock.json') }}
          restore-keys: ${{ runner.os }}-npm-

      - name: Cache Vite build artifacts
        uses: actions/cache@v4
        with:
          path: ui/node_modules/.vite
          key: ${{ runner.os }}-vite-${{ hashFiles('ui/package-lock.json', 'ui/vite.config.ts') }}
          restore-keys: ${{ runner.os }}-vite-

      - name: Build UI
        working-directory: ui
        run: |
          echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}' >> .npmrc
          npm ci --prefer-offline --no-audit --fund=false
          npm run build
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
          VITE_APP_VERSION: ${{ github.sha }}

      - name: Build and Test
        run: mvn clean verify -DskipITs -U --batch-mode

      - name: Deploy minter to Maven registry
        if: github.event_name == 'push'
        run: mvn deploy -DskipTests -DskipITs --batch-mode -pl .,cameleer-license-api,cameleer-server-core,cameleer-license-minter
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}

  docker:
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    container:
      image: gitea.siegeln.net/cameleer/cameleer-docker-builder:1
      credentials:
        username: cameleer
        password: ${{ secrets.REGISTRY_TOKEN }}
    steps:
      - name: Checkout
        run: |
          git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
      - name: Login to registry
        run: echo "$REGISTRY_TOKEN" | docker login gitea.siegeln.net -u cameleer --password-stdin
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
      - name: Compute branch slug
        run: |
          . .gitea/sanitize-branch.sh
          if [ "$GITHUB_REF_NAME" = "main" ]; then
            echo "BRANCH_SLUG=main" >> "$GITHUB_ENV"
            echo "IMAGE_TAGS=latest" >> "$GITHUB_ENV"
          else
            SLUG=$(sanitize_branch "$GITHUB_REF_NAME")
            echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
            echo "IMAGE_TAGS=branch-$SLUG" >> "$GITHUB_ENV"
          fi
      - name: Set up QEMU for cross-platform builds
        run: docker run --rm --privileged gitea.siegeln.net/cameleer/binfmt:1 --install all
      - name: Build and push server
        run: |
          docker buildx create --use --name cibuilder
          TAGS="-t gitea.siegeln.net/cameleer/cameleer-server:${{ github.sha }}"
          for TAG in $IMAGE_TAGS; do
            TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer-server:$TAG"
          done
          docker buildx build --platform linux/amd64 \
            --build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
            $TAGS \
            --cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server:buildcache \
            --cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server:buildcache,mode=max \
            --provenance=false \
            --push .
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
      - name: Build and push UI
        run: |
          TAGS="-t gitea.siegeln.net/cameleer/cameleer-server-ui:${{ github.sha }}"
          for TAG in $IMAGE_TAGS; do
            TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer-server-ui:$TAG"
          done
          SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
          docker buildx build --platform linux/amd64 \
            -f ui/Dockerfile \
            --build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
            --build-arg VITE_APP_VERSION="$SHORT_SHA" \
            $TAGS \
            --cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server-ui:buildcache \
            --cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer-server-ui:buildcache,mode=max \
            --provenance=false \
            --push ui/
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
      - name: Cleanup local Docker
        run: docker system prune -af --filter "until=24h"
        if: always()
      - name: Cleanup old container images
        run: |
          API="https://gitea.siegeln.net/api/v1"
          AUTH="Authorization: token ${REGISTRY_TOKEN}"
          CURRENT_SHA="${{ github.sha }}"
          # Build list of tags to keep
          KEEP_TAGS="latest buildcache $CURRENT_SHA"
          if [ "$BRANCH_SLUG" != "main" ]; then
            KEEP_TAGS="$KEEP_TAGS branch-$BRANCH_SLUG"
          fi
          for PKG in cameleer-server cameleer-server-ui; do
            curl -sf -H "$AUTH" "$API/packages/cameleer/container/$PKG" | \
              jq -r '.[] | "\(.id) \(.version)"' | \
              while read id version; do
                SHOULD_KEEP=false
                for KEEP in $KEEP_TAGS; do
                  if [ "$version" = "$KEEP" ]; then
                    SHOULD_KEEP=true
                    break
                  fi
                done
                if [ "$SHOULD_KEEP" = "false" ]; then
                  # Only clean up images for this branch
                  if [ "$BRANCH_SLUG" = "main" ] || echo "$version" | grep -q "branch-$BRANCH_SLUG"; then
                    echo "Deleting old image tag: $PKG:$version"
                    curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/$PKG/$version"
                  fi
                fi
              done
          done
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
        if: always()

  deploy:
    needs: docker
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    container:
      image: alpine/k8s:1.32.3
    steps:
      - name: Checkout
        run: |
          git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
      - name: Configure kubectl
        run: |
          mkdir -p ~/.kube
          echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
        env:
          KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
      - name: Deploy
        run: |
          kubectl create namespace cameleer --dry-run=client -o yaml | kubectl apply -f -

          kubectl create secret docker-registry gitea-registry \
            --namespace=cameleer \
            --docker-server=gitea.siegeln.net \
            --docker-username=cameleer \
            --docker-password="$REGISTRY_TOKEN" \
            --dry-run=client -o yaml | kubectl apply -f -

          kubectl create secret generic cameleer-auth \
            --namespace=cameleer \
            --from-literal=CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN="$CAMELEER_AUTH_TOKEN" \
            --from-literal=CAMELEER_SERVER_SECURITY_UIUSER="${CAMELEER_UI_USER:-admin}" \
            --from-literal=CAMELEER_SERVER_SECURITY_UIPASSWORD="${CAMELEER_UI_PASSWORD:-admin}" \
            --from-literal=CAMELEER_SERVER_SECURITY_JWTSECRET="${CAMELEER_JWT_SECRET}" \
            --dry-run=client -o yaml | kubectl apply -f -

          kubectl create secret generic cameleer-postgres-credentials \
            --namespace=cameleer \
            --from-literal=POSTGRES_USER="$POSTGRES_USER" \
            --from-literal=POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
            --from-literal=POSTGRES_DB="${POSTGRES_DB:-cameleer}" \
            --dry-run=client -o yaml | kubectl apply -f -

          kubectl create secret generic cameleer-logto-credentials \
            --namespace=cameleer \
            --from-literal=PG_USER="${LOGTO_PG_USER:-logto}" \
            --from-literal=PG_PASSWORD="${LOGTO_PG_PASSWORD}" \
            --from-literal=ENDPOINT="${LOGTO_ENDPOINT}" \
            --from-literal=ADMIN_ENDPOINT="${LOGTO_ADMIN_ENDPOINT}" \
            --dry-run=client -o yaml | kubectl apply -f -

          kubectl create secret generic cameleer-clickhouse-credentials \
            --namespace=cameleer \
            --from-literal=CLICKHOUSE_USER="${CLICKHOUSE_USER:-default}" \
            --from-literal=CLICKHOUSE_PASSWORD="$CLICKHOUSE_PASSWORD" \
            --dry-run=client -o yaml | kubectl apply -f -

          kubectl apply -f deploy/cameleer-postgres.yaml
          kubectl -n cameleer rollout status statefulset/cameleer-postgres --timeout=120s

          kubectl apply -f deploy/cameleer-clickhouse.yaml
          kubectl -n cameleer rollout status statefulset/cameleer-clickhouse --timeout=180s

          kubectl apply -f deploy/cameleer-logto.yaml
          kubectl -n cameleer rollout status deployment/cameleer-logto --timeout=180s

          kubectl apply -k deploy/overlays/main
          kubectl -n cameleer set image deployment/cameleer-server \
            server=gitea.siegeln.net/cameleer/cameleer-server:${{ github.sha }}
          kubectl -n cameleer rollout status deployment/cameleer-server --timeout=120s

          kubectl -n cameleer set image deployment/cameleer-ui \
            ui=gitea.siegeln.net/cameleer/cameleer-server-ui:${{ github.sha }}
          kubectl -n cameleer rollout status deployment/cameleer-ui --timeout=120s
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
          CAMELEER_AUTH_TOKEN: ${{ secrets.CAMELEER_AUTH_TOKEN }}
          CAMELEER_JWT_SECRET: ${{ secrets.CAMELEER_JWT_SECRET }}
          CAMELEER_UI_USER: ${{ secrets.CAMELEER_UI_USER }}
          CAMELEER_UI_PASSWORD: ${{ secrets.CAMELEER_UI_PASSWORD }}
          POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
          POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
          POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
          LOGTO_PG_USER: ${{ secrets.LOGTO_PG_USER }}
          LOGTO_PG_PASSWORD: ${{ secrets.LOGTO_PG_PASSWORD }}
          LOGTO_ENDPOINT: ${{ secrets.LOGTO_ENDPOINT }}
          LOGTO_ADMIN_ENDPOINT: ${{ secrets.LOGTO_ADMIN_ENDPOINT }}
          CLICKHOUSE_USER: ${{ secrets.CLICKHOUSE_USER }}
          CLICKHOUSE_PASSWORD: ${{ secrets.CLICKHOUSE_PASSWORD }}

  deploy-feature:
    needs: docker
    runs-on: ubuntu-latest
    if: github.ref != 'refs/heads/main' && github.event_name == 'push'
    container:
      image: alpine/k8s:1.32.3
    steps:
      - name: Checkout
        run: |
          git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
      - name: Configure kubectl
        run: |
          mkdir -p ~/.kube
          echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
        env:
          KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
      - name: Compute branch variables
        run: |
          . .gitea/sanitize-branch.sh
          SLUG=$(sanitize_branch "$GITHUB_REF_NAME")
          NS="cam-${SLUG}"
          SCHEMA="cam_$(echo $SLUG | tr '-' '_')"
          echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
          echo "BRANCH_NS=$NS" >> "$GITHUB_ENV"
          echo "BRANCH_SCHEMA=$SCHEMA" >> "$GITHUB_ENV"
      - name: Create namespace
        run: kubectl create namespace "$BRANCH_NS" --dry-run=client -o yaml | kubectl apply -f -
      - name: Copy secrets from cameleer namespace
        run: |
          for SECRET in gitea-registry cameleer-postgres-credentials cameleer-clickhouse-credentials cameleer-auth; do
            kubectl get secret "$SECRET" -n cameleer -o json \
              | jq 'del(.metadata.namespace, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.managedFields)' \
              | kubectl apply -n "$BRANCH_NS" -f -
          done
      - name: Substitute placeholders and deploy
        run: |
          # Work on a copy preserving the directory structure so ../../base resolves
          mkdir -p /tmp/feature-deploy/deploy/overlays
          cp -r deploy/base /tmp/feature-deploy/deploy/base
          cp -r deploy/overlays/feature /tmp/feature-deploy/deploy/overlays/feature
          # Substitute all BRANCH_* placeholders
          for f in /tmp/feature-deploy/deploy/overlays/feature/*.yaml; do
            sed -i \
              -e "s|BRANCH_NAMESPACE|${BRANCH_NS}|g" \
              -e "s|BRANCH_SCHEMA|${BRANCH_SCHEMA}|g" \
              -e "s|BRANCH_SLUG|${BRANCH_SLUG}|g" \
              -e "s|BRANCH_SHA|${{ github.sha }}|g" \
              "$f"
          done
          kubectl apply -k /tmp/feature-deploy/deploy/overlays/feature
      - name: Wait for init-job
        run: |
          kubectl -n "$BRANCH_NS" wait --for=condition=complete job/init-schema --timeout=60s || \
            echo "Warning: init-schema job did not complete in time"
      - name: Wait for server rollout
        run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer-server --timeout=120s
      - name: Wait for UI rollout
        run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer-ui --timeout=60s
      - name: Print deployment URLs
        run: |
          echo "===================================="
          echo "Feature branch deployed!"
          echo "API: http://${BRANCH_SLUG}-api.cameleer.siegeln.net"
          echo "UI:  http://${BRANCH_SLUG}.cameleer.siegeln.net"
          echo "===================================="
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}

  cleanup-branch:
    runs-on: ubuntu-latest
    if: github.event_name == 'delete' && github.event.ref_type == 'branch'
    container:
      image: alpine/k8s:1.32.3
    steps:
      - name: Configure kubectl
        run: |
          mkdir -p ~/.kube
          echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
        env:
          KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
      - name: Compute branch variables
        run: |
          sanitize_branch() {
            echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
              | tr '[:upper:]' '[:lower:]' \
              | sed 's/[^a-z0-9-]/-/g' \
              | sed 's/--*/-/g; s/^-//; s/-$//' \
              | cut -c1-20 \
              | sed 's/-$//'
          }
          SLUG=$(sanitize_branch "${{ github.event.ref }}")
          NS="cam-${SLUG}"
          SCHEMA="cam_$(echo $SLUG | tr '-' '_')"
          echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
          echo "BRANCH_NS=$NS" >> "$GITHUB_ENV"
          echo "BRANCH_SCHEMA=$SCHEMA" >> "$GITHUB_ENV"
      - name: Delete namespace
        run: kubectl delete namespace "$BRANCH_NS" --ignore-not-found
      - name: Drop PostgreSQL schema
        run: |
          kubectl run cleanup-schema-${BRANCH_SLUG} \
            --namespace=cameleer \
            --image=postgres:16 \
            --restart=Never \
            --env="PGPASSWORD=$(kubectl get secret cameleer-postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d)" \
            --command -- sh -c "psql -h cameleer-postgres -U $(kubectl get secret cameleer-postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_USER}' | base64 -d) -d cameleer -c 'DROP SCHEMA IF EXISTS ${BRANCH_SCHEMA} CASCADE'"
          kubectl wait --for=condition=Ready pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=30s || true
          kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=60s || true
          kubectl delete pod cleanup-schema-${BRANCH_SLUG} -n cameleer --ignore-not-found
      - name: Cleanup Docker images
        run: |
          API="https://gitea.siegeln.net/api/v1"
          AUTH="Authorization: token ${REGISTRY_TOKEN}"
          for PKG in cameleer-server cameleer-server-ui; do
            # Delete branch-specific tag
            curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/$PKG/branch-${BRANCH_SLUG}" || true
          done
        env:
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
