# .gitea/workflows/build-test-deploy.yml
# Unified CI/CD workflow for git.stella-ops.org (Feedser monorepo)

name: Build Test Deploy

on:
  push:
    branches: [ main ]
    paths:
      - 'src/**'
      - 'docs/**'
      - 'scripts/**'
      - 'Directory.Build.props'
      - 'Directory.Build.targets'
      - 'global.json'
      - '.gitea/workflows/**'
  pull_request:
    branches: [ main, develop ]
    paths:
      - 'src/**'
      - 'docs/**'
      - 'scripts/**'
      - '.gitea/workflows/**'
  workflow_dispatch:
    inputs:
      force_deploy:
        description: 'Ignore branch checks and run the deploy stage'
        required: false
        default: 'false'
        type: boolean

env:
  DOTNET_VERSION: '10.0.100-rc.1.25451.107'
  BUILD_CONFIGURATION: Release
  CI_CACHE_ROOT: /data/.cache/stella-ops/feedser
  RUNNER_TOOL_CACHE: /toolcache

jobs:
  build-test:
    runs-on: ubuntu-22.04
    environment: ${{ github.event_name == 'pull_request' && 'preview' || 'staging' }}
    env:
      PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/webservice
      AUTHORITY_PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/authority
      TEST_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup .NET ${{ env.DOTNET_VERSION }}
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}
          include-prerelease: true

      - name: Restore dependencies
        run: dotnet restore src/StellaOps.Feedser.sln

      - name: Build solution (warnings as errors)
        run: dotnet build src/StellaOps.Feedser.sln --configuration $BUILD_CONFIGURATION --no-restore -warnaserror

      - name: Run unit and integration tests
        run: |
          mkdir -p "$TEST_RESULTS_DIR"
          dotnet test src/StellaOps.Feedser.sln \
            --configuration $BUILD_CONFIGURATION \
            --no-build \
            --logger "trx;LogFileName=stellaops-feedser-tests.trx" \
            --results-directory "$TEST_RESULTS_DIR"

      - name: Publish Feedser web service
        run: |
          mkdir -p "$PUBLISH_DIR"
          dotnet publish src/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj \
            --configuration $BUILD_CONFIGURATION \
            --no-build \
            --output "$PUBLISH_DIR"

      - name: Upload published artifacts
        uses: actions/upload-artifact@v4
        with:
          name: feedser-publish
          path: ${{ env.PUBLISH_DIR }}
          if-no-files-found: error
          retention-days: 7

      - name: Restore Authority solution
        run: dotnet restore src/StellaOps.Authority/StellaOps.Authority.sln

      - name: Build Authority solution
        run: dotnet build src/StellaOps.Authority/StellaOps.Authority.sln --configuration $BUILD_CONFIGURATION --no-restore -warnaserror

      - name: Run Authority tests
        run: |
          dotnet test src/StellaOps.Configuration.Tests/StellaOps.Configuration.Tests.csproj \
            --configuration $BUILD_CONFIGURATION \
            --no-build \
            --logger "trx;LogFileName=stellaops-authority-tests.trx" \
            --results-directory "$TEST_RESULTS_DIR"

      - name: Publish Authority web service
        run: |
          mkdir -p "$AUTHORITY_PUBLISH_DIR"
          dotnet publish src/StellaOps.Authority/StellaOps.Authority/StellaOps.Authority.csproj \
            --configuration $BUILD_CONFIGURATION \
            --no-build \
            --output "$AUTHORITY_PUBLISH_DIR"

      - name: Upload Authority artifacts
        uses: actions/upload-artifact@v4
        with:
          name: authority-publish
          path: ${{ env.AUTHORITY_PUBLISH_DIR }}
          if-no-files-found: error
          retention-days: 7

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: feedser-test-results
          path: ${{ env.TEST_RESULTS_DIR }}
          if-no-files-found: ignore
          retention-days: 7

  authority-container:
    runs-on: ubuntu-22.04
    needs: build-test
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Validate Authority compose file
        run: docker compose -f ops/authority/docker-compose.authority.yaml config

      - name: Build Authority container image
        run: docker build -f ops/authority/Dockerfile -t stellaops-authority:ci .

  docs:
    runs-on: ubuntu-22.04
    env:
      DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-site
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install documentation dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install markdown pygments

      - name: Render documentation bundle
        run: |
          python scripts/render_docs.py --source docs --output "$DOCS_OUTPUT_DIR" --clean

      - name: Upload documentation artifact
        uses: actions/upload-artifact@v4
        with:
          name: feedser-docs-site
          path: ${{ env.DOCS_OUTPUT_DIR }}
          if-no-files-found: error
          retention-days: 7

  deploy:
    runs-on: ubuntu-22.04
    needs: [build-test, docs]
    if: >-
      needs.build-test.result == 'success' &&
      needs.docs.result == 'success' &&
      (
        (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
        github.event_name == 'workflow_dispatch'
      )
    environment: staging
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          sparse-checkout: |
            scripts
            .gitea/workflows
          sparse-checkout-cone-mode: true

      - name: Check if deployment should proceed
        id: check-deploy
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            if [ "${{ github.event.inputs.force_deploy }}" = "true" ]; then
              echo "should-deploy=true" >> $GITHUB_OUTPUT
              echo "✅ Manual deployment requested"
            else
              echo "should-deploy=false" >> $GITHUB_OUTPUT
              echo "ℹ️ Manual dispatch without force_deploy=true — skipping"
            fi
          elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
            echo "should-deploy=true" >> $GITHUB_OUTPUT
            echo "✅ Deploying latest main branch build"
          else
            echo "should-deploy=false" >> $GITHUB_OUTPUT
            echo "ℹ️ Deployment restricted to main branch"
          fi

      - name: Resolve deployment credentials
        id: params
        if: steps.check-deploy.outputs.should-deploy == 'true'
        run: |
          missing=()

          host="${{ secrets.STAGING_DEPLOYMENT_HOST }}"
          if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi
          if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
          if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
          if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi

          user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}"
          if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi
          if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
          if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
          if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi

          path="${{ secrets.STAGING_DEPLOYMENT_PATH }}"
          if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi

          docs_path="${{ secrets.STAGING_DOCS_PATH }}"
          if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi

          key="${{ secrets.STAGING_DEPLOYMENT_KEY }}"
          if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
          if [ -z "$key" ]; then key="${{ vars.STAGING_DEPLOYMENT_KEY }}"; fi
          if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
          if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi

          if [ ${#missing[@]} -gt 0 ]; then
            echo "❌ Missing deployment configuration: ${missing[*]}"
            exit 1
          fi

          key_file="$RUNNER_TEMP/staging_deploy_key"
          printf '%s\n' "$key" > "$key_file"
          chmod 600 "$key_file"

          echo "host=$host" >> $GITHUB_OUTPUT
          echo "user=$user" >> $GITHUB_OUTPUT
          echo "path=$path" >> $GITHUB_OUTPUT
          echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
          echo "key-file=$key_file" >> $GITHUB_OUTPUT

      - name: Download service artifact
        if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs.path != ''
        uses: actions/download-artifact@v4
        with:
          name: feedser-publish
          path: artifacts/service

      - name: Download documentation artifact
        if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs['docs-path'] != ''
        uses: actions/download-artifact@v4
        with:
          name: feedser-docs-site
          path: artifacts/docs

      - name: Install rsync
        if: steps.check-deploy.outputs.should-deploy == 'true'
        run: |
          if command -v rsync >/dev/null 2>&1; then
            exit 0
          fi
          CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt"
          mkdir -p "$CACHE_DIR"
          KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)"
          DEB_DIR="$CACHE_DIR/$KEY"
          mkdir -p "$DEB_DIR"
          if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then
            apt-get update
            apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb
          else
            apt-get update
            apt-get download rsync libpopt0
            mv rsync*.deb libpopt0*.deb "$DEB_DIR"/
            dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y
          fi

      - name: Deploy service bundle
        if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs.path != ''
        env:
          HOST: ${{ steps.params.outputs.host }}
          USER: ${{ steps.params.outputs.user }}
          TARGET: ${{ steps.params.outputs.path }}
          KEY_FILE: ${{ steps.params.outputs['key-file'] }}
        run: |
          SERVICE_DIR="artifacts/service/feedser-publish"
          if [ ! -d "$SERVICE_DIR" ]; then
            echo "❌ Service artifact directory missing ($SERVICE_DIR)"
            exit 1
          fi
          echo "🚀 Deploying Feedser web service to $HOST:$TARGET"
          rsync -az --delete \
            -e "ssh -i $KEY_FILE -o StrictHostKeyChecking=no" \
            "$SERVICE_DIR"/ \
            "$USER@$HOST:$TARGET/"

      - name: Deploy documentation bundle
        if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs['docs-path'] != ''
        env:
          HOST: ${{ steps.params.outputs.host }}
          USER: ${{ steps.params.outputs.user }}
          DOCS_TARGET: ${{ steps.params.outputs['docs-path'] }}
          KEY_FILE: ${{ steps.params.outputs['key-file'] }}
        run: |
          DOCS_DIR="artifacts/docs/feedser-docs-site"
          if [ ! -d "$DOCS_DIR" ]; then
            echo "❌ Documentation artifact directory missing ($DOCS_DIR)"
            exit 1
          fi
          echo "📚 Deploying documentation bundle to $HOST:$DOCS_TARGET"
          rsync -az --delete \
            -e "ssh -i $KEY_FILE -o StrictHostKeyChecking=no" \
            "$DOCS_DIR"/ \
            "$USER@$HOST:$DOCS_TARGET/"

      - name: Deployment summary
        if: steps.check-deploy.outputs.should-deploy == 'true'
        run: |
          echo "✅ Deployment completed"
          echo "   Host: ${{ steps.params.outputs.host }}"
          echo "   Service path: ${{ steps.params.outputs.path || '(skipped)' }}"
          echo "   Docs path: ${{ steps.params.outputs['docs-path'] || '(skipped)' }}"

      - name: Deployment skipped summary
        if: steps.check-deploy.outputs.should-deploy != 'true'
        run: |
          echo "ℹ️ Deployment stage skipped"
          echo "   Event: ${{ github.event_name }}"
          echo "   Ref: ${{ github.ref }}"
