name: CI

on:
  pull_request:
  push:
    branches-ignore:
      - main
      - master

jobs:
  # -------------------------------------------------------------------
  # Setup: Provision an ephemeral Hetzner Cloud VM as a self-hosted runner
  # -------------------------------------------------------------------
  setup-runner:
    name: Provision Hetzner Runner
    runs-on: ubuntu-latest
    outputs:
      runner_name: ${{ '{{' }} steps.create.outputs.runner_name {{ '}}' }}
      server_id: ${{ '{{' }} steps.create.outputs.server_id {{ '}}' }}
    steps:
      - name: Install hcloud CLI
        run: |
          curl -sL https://github.com/hetznercloud/cli/releases/latest/download/hcloud-linux-amd64.tar.gz | tar xz
          sudo mv hcloud /usr/local/bin/

      - name: Generate runner registration token
        id: token
        env:
          GH_TOKEN: ${{ '{{' }} secrets.RUNNER_GITHUB_TOKEN {{ '}}' }}
        run: |
          TOKEN=$(gh api -X POST repos/${{ '{{' }} github.repository {{ '}}' }}/actions/runners/registration-token --jq '.token')
          echo "::add-mask::$TOKEN"
          echo "token=$TOKEN" >> "$GITHUB_OUTPUT"

      - name: Create Hetzner VM
        id: create
        env:
          HCLOUD_TOKEN: ${{ '{{' }} secrets.HCLOUD_TOKEN {{ '}}' }}
        run: |
          RUNNER_NAME="prisme-runner-${{ '{{' }} github.run_id {{ '}}' }}-${{ '{{' }} github.run_attempt {{ '}}' }}"
          {% if runner_image.startswith("ca") %}
          ARCH="arm64"
          {% else %}
          ARCH="x64"
          {% endif %}

          LABELS="self-hosted,hetzner{% for k, v in runner_labels.items() %},{{ k }}={{ v }}{% endfor %}"

          cat > /tmp/cloud-init.yml << 'CLOUD_INIT'
          #cloud-config
          package_update: true
          packages:
            - curl
            - jq
            - docker.io
          runcmd:
            - useradd -m -s /bin/bash runner
            - usermod -aG docker runner
            - mkdir -p /home/runner/actions-runner
            - cd /home/runner/actions-runner
            - 'curl -sL "https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-ARCH_PLACEHOLDER-2.321.0.tar.gz" | tar xz -C /home/runner/actions-runner'
            - chown -R runner:runner /home/runner/actions-runner
            - |
              su - runner -c 'cd /home/runner/actions-runner && ./config.sh \
                --url "https://github.com/REPO_PLACEHOLDER" \
                --token "TOKEN_PLACEHOLDER" \
                --name "NAME_PLACEHOLDER" \
                --labels "LABELS_PLACEHOLDER" \
                --ephemeral \
                --unattended'
            - su - runner -c 'cd /home/runner/actions-runner && ./run.sh'
          CLOUD_INIT

          {% if runner_server_type.startswith("ca") %}
          sed -i "s/ARCH_PLACEHOLDER/arm64/g" /tmp/cloud-init.yml
          {% else %}
          sed -i "s/ARCH_PLACEHOLDER/x64/g" /tmp/cloud-init.yml
          {% endif %}
          sed -i "s|REPO_PLACEHOLDER|${{ '{{' }} github.repository {{ '}}' }}|g" /tmp/cloud-init.yml
          sed -i "s|TOKEN_PLACEHOLDER|${{ '{{' }} steps.token.outputs.token {{ '}}' }}|g" /tmp/cloud-init.yml
          sed -i "s|NAME_PLACEHOLDER|$RUNNER_NAME|g" /tmp/cloud-init.yml
          sed -i "s|LABELS_PLACEHOLDER|$LABELS|g" /tmp/cloud-init.yml

          SERVER_JSON=$(hcloud server create \
            --name "$RUNNER_NAME" \
            --type {{ runner_server_type }} \
            --location {{ runner_location }} \
            --image {{ runner_image }} \
            --user-data-from-file /tmp/cloud-init.yml \
            --label managed-by=prisme \
            --label purpose=ci-runner \
            --label run-id=${{ '{{' }} github.run_id {{ '}}' }} \
            -o json)

          SERVER_ID=$(echo "$SERVER_JSON" | jq -r '.server.id')
          echo "runner_name=$RUNNER_NAME" >> "$GITHUB_OUTPUT"
          echo "server_id=$SERVER_ID" >> "$GITHUB_OUTPUT"

      - name: Wait for runner to come online
        env:
          GH_TOKEN: ${{ '{{' }} secrets.RUNNER_GITHUB_TOKEN {{ '}}' }}
        run: |
          RUNNER_NAME="${{ '{{' }} steps.create.outputs.runner_name {{ '}}' }}"
          echo "Waiting for runner $RUNNER_NAME to register..."
          for i in $(seq 1 60); do
            ONLINE=$(gh api repos/${{ '{{' }} github.repository {{ '}}' }}/actions/runners --jq ".runners[] | select(.name==\"$RUNNER_NAME\") | .status" 2>/dev/null || true)
            if [ "$ONLINE" = "online" ]; then
              echo "Runner is online!"
              exit 0
            fi
            echo "  Attempt $i/60 - runner not ready yet..."
            sleep 10
          done
          echo "::error::Runner did not come online within 10 minutes"
          exit 1

  # -------------------------------------------------------------------
  # Backend jobs — run on Hetzner self-hosted runner
  # -------------------------------------------------------------------
  backend-lint:
    name: Backend Lint
    needs: [setup-runner]
    runs-on: [self-hosted, hetzner]
    defaults:
      run:
        working-directory: {{ backend_path }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '{{ python_version }}'

      - name: Install uv
        uses: astral-sh/setup-uv@v4

      - name: Install dependencies
        run: uv sync --extra dev

      - name: Run ruff (lint)
        run: uv run ruff check .

      - name: Run ruff (format check)
        run: uv run ruff format --check .

  backend-typecheck:
    name: Backend Type Check
    needs: [setup-runner]
    runs-on: [self-hosted, hetzner]
    defaults:
      run:
        working-directory: {{ backend_path }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '{{ python_version }}'

      - name: Install uv
        uses: astral-sh/setup-uv@v4

      - name: Install dependencies
        run: uv sync --extra dev

      - name: Run mypy
        run: uv run mypy .

  backend-test:
    name: Backend Tests
    needs: [setup-runner]
    runs-on: [self-hosted, hetzner]
    defaults:
      run:
        working-directory: {{ backend_path }}

    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: {{ project_name_snake }}_test
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      {% if use_redis %}
      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379
      {% endif %}

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '{{ python_version }}'

      - name: Install uv
        uses: astral-sh/setup-uv@v4

      - name: Install dependencies
        run: uv sync --extra dev

      - name: Run tests
        env:
          DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/{{ project_name_snake }}_test
          {% if use_redis %}
          REDIS_URL: redis://localhost:6379/0
          {% endif %}
          ENVIRONMENT: test
        run: uv run pytest --cov --cov-report=xml --cov-report=term

      {% if enable_codecov %}
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        if: always()
        with:
          file: ./{{ backend_path }}/coverage.xml
          flags: backend
          token: ${{ '{{' }} secrets.CODECOV_TOKEN {{ '}}' }}
      {% endif %}

  migration-check:
    name: Migration Check
    needs: [setup-runner]
    runs-on: [self-hosted, hetzner]
    defaults:
      run:
        working-directory: {{ backend_path }}
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: {{ project_name_snake }}_migration_check
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '{{ python_version }}'
      - name: Install uv
        uses: astral-sh/setup-uv@v4
      - name: Install dependencies
        run: uv sync --extra dev
      - name: Apply migrations to fresh database
        env:
          DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/{{ project_name_snake }}_migration_check
        run: uv run alembic -c ../../alembic.ini upgrade head
      - name: Check for model/migration drift
        env:
          DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/{{ project_name_snake }}_migration_check
        run: uv run alembic -c ../../alembic.ini check

  {% if include_frontend %}
  frontend-lint:
    name: Frontend Lint
    needs: [setup-runner]
    runs-on: [self-hosted, hetzner]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '{{ node_version }}'
          cache: 'npm'
          cache-dependency-path: {{ frontend_path }}/package-lock.json

      - name: Install dependencies
        working-directory: {{ frontend_path }}
        run: npm ci

      - name: Run ESLint
        working-directory: {{ frontend_path }}
        run: npm run lint

  frontend-typecheck:
    name: Frontend Type Check
    needs: [setup-runner]
    runs-on: [self-hosted, hetzner]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '{{ node_version }}'
          cache: 'npm'
          cache-dependency-path: {{ frontend_path }}/package-lock.json

      - name: Install dependencies
        working-directory: {{ frontend_path }}
        run: npm ci

      - name: Run TypeScript compiler
        working-directory: {{ frontend_path }}
        run: npm run typecheck

  frontend-test:
    name: Frontend Tests
    needs: [setup-runner]
    runs-on: [self-hosted, hetzner]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '{{ node_version }}'
          cache: 'npm'
          cache-dependency-path: {{ frontend_path }}/package-lock.json

      - name: Install dependencies
        working-directory: {{ frontend_path }}
        run: npm ci

      - name: Run tests
        working-directory: {{ frontend_path }}
        run: npm run test:coverage

      {% if enable_codecov %}
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        if: always()
        with:
          file: ./{{ frontend_path }}/coverage/coverage-final.json
          flags: frontend
          token: ${{ '{{' }} secrets.CODECOV_TOKEN {{ '}}' }}
      {% endif %}
  {% endif %}

  # -------------------------------------------------------------------
  # Teardown: Destroy the Hetzner VM after all jobs finish
  # -------------------------------------------------------------------
  teardown-runner:
    name: Destroy Hetzner Runner
    needs:
      - setup-runner
      - backend-lint
      - backend-typecheck
      - backend-test
      - migration-check
      {% if include_frontend %}
      - frontend-lint
      - frontend-typecheck
      - frontend-test
      {% endif %}
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Install hcloud CLI
        run: |
          curl -sL https://github.com/hetznercloud/cli/releases/latest/download/hcloud-linux-amd64.tar.gz | tar xz
          sudo mv hcloud /usr/local/bin/

      - name: Destroy runner VM
        env:
          HCLOUD_TOKEN: ${{ '{{' }} secrets.HCLOUD_TOKEN {{ '}}' }}
        run: |
          SERVER_ID="${{ '{{' }} needs.setup-runner.outputs.server_id {{ '}}' }}"
          if [ -n "$SERVER_ID" ] && [ "$SERVER_ID" != "" ]; then
            echo "Destroying Hetzner server $SERVER_ID..."
            hcloud server delete "$SERVER_ID" || true
            echo "Server destroyed."
          else
            echo "No server ID found, skipping cleanup."
          fi

      - name: Remove runner registration
        if: always()
        env:
          GH_TOKEN: ${{ '{{' }} secrets.RUNNER_GITHUB_TOKEN {{ '}}' }}
        run: |
          RUNNER_NAME="${{ '{{' }} needs.setup-runner.outputs.runner_name {{ '}}' }}"
          if [ -n "$RUNNER_NAME" ]; then
            RUNNER_ID=$(gh api repos/${{ '{{' }} github.repository {{ '}}' }}/actions/runners --jq ".runners[] | select(.name==\"$RUNNER_NAME\") | .id" 2>/dev/null || true)
            if [ -n "$RUNNER_ID" ]; then
              gh api -X DELETE repos/${{ '{{' }} github.repository {{ '}}' }}/actions/runners/$RUNNER_ID || true
              echo "Runner registration removed."
            fi
          fi
