stages:
  - test
  - build
  - deploy

test:
  stage: test
  image: laravelsail/php84-composer

  variables:
    APP_ENV: testing
    APP_KEY: base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
    DB_CONNECTION: sqlite
    DB_DATABASE: ":memory:"

  before_script:
    - apt-get update && apt-get install -y libicu-dev
    - docker-php-ext-configure intl && docker-php-ext-install intl
    - composer install --no-interaction --prefer-dist

  script:
    - php artisan test

  rules:
    - when: always

docker-build-push:
  stage: build
  image: docker:24
  services:
    - docker:24-dind

  variables:
    DOCKER_TLS_CERTDIR: "/certs"
    IMAGE_TAG: "$CI_REGISTRY_IMAGE:${CI_COMMIT_SHORT_SHA}"
    IMAGE_LATEST: "$CI_REGISTRY_IMAGE:latest"

  before_script:
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"

  script:
    - docker build -t "$IMAGE_TAG" -t "$IMAGE_LATEST" .
    - docker push "$IMAGE_TAG"
    - docker push "$IMAGE_LATEST"

  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

deploy-to-portainer:
  stage: deploy
  image: alpine:latest
  needs:
    - docker-build-push

  before_script:
    - apk add --no-cache curl jq gettext

  script:
    - envsubst < docker-compose.prod.yml > docker-compose.rendered.yml

    - |
      set -eu

      PORTAINER_BASE_URL="${PORTAINER_URL%/}"

      for required_var in PORTAINER_URL PORTAINER_API_KEY PORTAINER_ENDPOINT_ID PORTAINER_STACK_NAME; do
        eval "required_value=\${$required_var:-}"
        if [ -z "$required_value" ]; then
          echo "Missing required CI variable: ${required_var}"
          exit 1
        fi
      done

      STACKS_RESPONSE=$(curl -sS -w "\n%{http_code}" \
        -H "X-API-Key: $PORTAINER_API_KEY" \
        "$PORTAINER_BASE_URL/api/stacks")
      STACKS_HTTP_CODE=$(echo "$STACKS_RESPONSE" | tail -n1)
      STACKS_JSON=$(echo "$STACKS_RESPONSE" | sed '$d')

      if [ "$STACKS_HTTP_CODE" -ge 400 ]; then
        echo "Failed to list stacks on Portainer (HTTP $STACKS_HTTP_CODE)"
        echo "$STACKS_JSON"
        exit 1
      fi

      echo "$STACKS_JSON" | jq -e . >/dev/null
      STACK_ID=$(echo "$STACKS_JSON" | jq ".[] | select(.Name == \"$PORTAINER_STACK_NAME\") | .Id")
      STACK_FILE_PATH="docker-compose.rendered.yml"

      if [ -z "$STACK_ID" ]; then
        JSON_PAYLOAD=$(jq -n \
          --arg name "$PORTAINER_STACK_NAME" \
          --rawfile content "$STACK_FILE_PATH" \
          '{name: $name, stackFileContent: $content, prune: true, fromAppTemplate: false}')

        CREATE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
          -X POST "$PORTAINER_BASE_URL/api/stacks/create/standalone/string?endpointId=$PORTAINER_ENDPOINT_ID" \
          -H "X-API-Key: $PORTAINER_API_KEY" \
          -H "Content-Type: application/json" \
          --data "$JSON_PAYLOAD")
        CREATE_HTTP_CODE=$(echo "$CREATE_RESPONSE" | tail -n1)
        CREATE_BODY=$(echo "$CREATE_RESPONSE" | sed '$d')

        if [ "$CREATE_HTTP_CODE" -ge 400 ]; then
          echo "Portainer stack creation failed (HTTP $CREATE_HTTP_CODE)"
          echo "$CREATE_BODY"
          exit 1
        fi

        echo "Stack created successfully."
      else
        JSON_PAYLOAD=$(jq -n \
          --arg id "$STACK_ID" \
          --rawfile content "$STACK_FILE_PATH" \
          '{id: ($id|tonumber), stackFileContent: $content, prune: false, pullImage: true}')

        UPDATE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
          -X PUT "$PORTAINER_BASE_URL/api/stacks/$STACK_ID?endpointId=$PORTAINER_ENDPOINT_ID&method=string" \
          -H "X-API-Key: $PORTAINER_API_KEY" \
          -H "Content-Type: application/json" \
          --data "$JSON_PAYLOAD")
        UPDATE_HTTP_CODE=$(echo "$UPDATE_RESPONSE" | tail -n1)
        UPDATE_BODY=$(echo "$UPDATE_RESPONSE" | sed '$d')

        if [ "$UPDATE_HTTP_CODE" -ge 400 ]; then
          echo "Portainer stack update failed (HTTP $UPDATE_HTTP_CODE)"
          echo "$UPDATE_BODY"
          exit 1
        fi

        echo "Stack updated successfully."
      fi

  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
