# EN: Deploy GoodGo Platform production services to Kubernetes production
# VI: Trien khai cac service production cua GoodGo Platform len K8s production
name: Deploy to Production

on:
  push:
    branches:
      - main
    paths:
      - 'services/iam-service-net/**'
      - 'services/merchant-service-net/**'
      - 'services/order-service-net/**'
      - 'services/fnb-engine-net/**'
      - 'services/inventory-service-net/**'
      - 'services/wallet-service-net/**'
      - 'services/catalog-service-net/**'
      - 'services/booking-service-net/**'
      - 'apps/web-client-tpos-net/**'
      - 'deployments/production/**'
  workflow_dispatch:
    inputs:
      service:
        description: 'Service to deploy (leave empty for all changed)'
        required: false
        default: ''
        type: choice
        options:
          - ''
          - iam-service
          - merchant-service
          - order-service
          - fnb-engine
          - inventory-service
          - wallet-service
          - catalog-service
          - booking-service
          - pos-web

env:
  REGISTRY: docker.io
  NAMESPACE: production

jobs:
  # =========================================================================
  # EN: Detect which services changed
  # VI: Phat hien service nao thay doi
  # =========================================================================
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      services: ${{ steps.changes.outputs.services }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Detect changed services
        id: changes
        run: |
          if [ -n "${{ github.event.inputs.service }}" ]; then
            echo 'services=["${{ github.event.inputs.service }}"]' >> $GITHUB_OUTPUT
            exit 0
          fi

          SERVICES=()
          CHANGED=$(git diff --name-only HEAD~1 HEAD)

          declare -A SERVICE_MAP=(
            ["services/iam-service-net"]="iam-service"
            ["services/merchant-service-net"]="merchant-service"
            ["services/order-service-net"]="order-service"
            ["services/fnb-engine-net"]="fnb-engine"
            ["services/inventory-service-net"]="inventory-service"
            ["services/wallet-service-net"]="wallet-service"
            ["services/catalog-service-net"]="catalog-service"
            ["services/booking-service-net"]="booking-service"
            ["apps/web-client-tpos-net"]="pos-web"
          )

          for path in "${!SERVICE_MAP[@]}"; do
            if echo "$CHANGED" | grep -q "^${path}/"; then
              SERVICES+=("\"${SERVICE_MAP[$path]}\"")
            fi
          done

          # EN: If deployment configs changed, deploy all
          # VI: Neu cau hinh deployment thay doi, deploy tat ca
          if echo "$CHANGED" | grep -q "^deployments/production/"; then
            SERVICES=("\"iam-service\"" "\"merchant-service\"" "\"order-service\"" "\"fnb-engine\"" "\"inventory-service\"" "\"wallet-service\"" "\"catalog-service\"" "\"booking-service\"" "\"pos-web\"")
          fi

          if [ ${#SERVICES[@]} -eq 0 ]; then
            echo 'services=[]' >> $GITHUB_OUTPUT
          else
            JOINED=$(IFS=,; echo "${SERVICES[*]}")
            echo "services=[${JOINED}]" >> $GITHUB_OUTPUT
          fi

  # =========================================================================
  # EN: Build & Push Docker Images (tagged with commit SHA, never :latest)
  # VI: Build & Push Docker Images (tag bang commit SHA, khong dung :latest)
  # =========================================================================
  build-and-push:
    needs: detect-changes
    if: needs.detect-changes.outputs.services != '[]'
    runs-on: ubuntu-latest
    # EN: Require production environment approval
    # VI: Yeu cau phe duyet moi truong production
    environment: production
    strategy:
      matrix:
        service: ${{ fromJSON(needs.detect-changes.outputs.services) }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Determine build context
        id: context
        run: |
          declare -A CONTEXT_MAP=(
            ["iam-service"]="./services/iam-service-net"
            ["merchant-service"]="./services/merchant-service-net"
            ["order-service"]="./services/order-service-net"
            ["fnb-engine"]="./services/fnb-engine-net"
            ["inventory-service"]="./services/inventory-service-net"
            ["wallet-service"]="./services/wallet-service-net"
            ["catalog-service"]="./services/catalog-service-net"
            ["booking-service"]="./services/booking-service-net"
            ["pos-web"]="./apps/web-client-tpos-net"
          )

          declare -A IMAGE_MAP=(
            ["iam-service"]="goodgo/iam-service-net"
            ["merchant-service"]="goodgo/merchant-service-net"
            ["order-service"]="goodgo/order-service-net"
            ["fnb-engine"]="goodgo/fnb-engine-net"
            ["inventory-service"]="goodgo/inventory-service-net"
            ["wallet-service"]="goodgo/wallet-service-net"
            ["catalog-service"]="goodgo/catalog-service-net"
            ["booking-service"]="goodgo/booking-service-net"
            ["pos-web"]="goodgo/web-client-tpos-net"
          )

          echo "context=${CONTEXT_MAP[${{ matrix.service }}]}" >> $GITHUB_OUTPUT
          echo "image=${IMAGE_MAP[${{ matrix.service }}]}" >> $GITHUB_OUTPUT

      # EN: Tag with commit SHA for production (never use :latest in prod)
      # VI: Tag bang commit SHA cho production (khong bao gio dung :latest trong prod)
      - name: Build and push ${{ matrix.service }}
        uses: docker/build-push-action@v5
        with:
          context: ${{ steps.context.outputs.context }}
          push: true
          tags: |
            ${{ steps.context.outputs.image }}:${{ github.sha }}
            ${{ steps.context.outputs.image }}:production
          cache-from: type=registry,ref=${{ steps.context.outputs.image }}:buildcache-prod
          cache-to: type=registry,ref=${{ steps.context.outputs.image }}:buildcache-prod,mode=max

  # =========================================================================
  # EN: Run Database Migrations
  # VI: Chay Database Migrations
  # =========================================================================
  migrations:
    needs: [detect-changes, build-and-push]
    if: needs.detect-changes.outputs.services != '[]'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Install EF Core tools
        run: dotnet tool install --global dotnet-ef || true

      - name: Run IAM migrations
        if: contains(needs.detect-changes.outputs.services, 'iam-service')
        run: |
          dotnet ef database update \
            --project services/iam-service-net/src/IamService.Infrastructure/IamService.Infrastructure.csproj \
            --startup-project services/iam-service-net/src/IamService.API/IamService.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_IAM_DATABASE_URL_PRODUCTION }}

      - name: Run Merchant migrations
        if: contains(needs.detect-changes.outputs.services, 'merchant-service')
        run: |
          dotnet ef database update \
            --project services/merchant-service-net/src/MerchantService.Infrastructure/MerchantService.Infrastructure.csproj \
            --startup-project services/merchant-service-net/src/MerchantService.API/MerchantService.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_MERCHANT_DATABASE_URL_PRODUCTION }}

      - name: Run Order migrations
        if: contains(needs.detect-changes.outputs.services, 'order-service')
        run: |
          dotnet ef database update \
            --project services/order-service-net/src/OrderService.Infrastructure/OrderService.Infrastructure.csproj \
            --startup-project services/order-service-net/src/OrderService.API/OrderService.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_ORDER_DATABASE_URL_PRODUCTION }}

      - name: Run FnB Engine migrations
        if: contains(needs.detect-changes.outputs.services, 'fnb-engine')
        run: |
          dotnet ef database update \
            --project services/fnb-engine-net/src/FnbEngine.Infrastructure/FnbEngine.Infrastructure.csproj \
            --startup-project services/fnb-engine-net/src/FnbEngine.API/FnbEngine.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_FNB_DATABASE_URL_PRODUCTION }}

      - name: Run Inventory migrations
        if: contains(needs.detect-changes.outputs.services, 'inventory-service')
        run: |
          dotnet ef database update \
            --project services/inventory-service-net/src/InventoryService.Infrastructure/InventoryService.Infrastructure.csproj \
            --startup-project services/inventory-service-net/src/InventoryService.API/InventoryService.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_INVENTORY_DATABASE_URL_PRODUCTION }}

      - name: Run Wallet migrations
        if: contains(needs.detect-changes.outputs.services, 'wallet-service')
        run: |
          dotnet ef database update \
            --project services/wallet-service-net/src/WalletService.Infrastructure/WalletService.Infrastructure.csproj \
            --startup-project services/wallet-service-net/src/WalletService.API/WalletService.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_WALLET_DATABASE_URL_PRODUCTION }}

      - name: Run Catalog migrations
        if: contains(needs.detect-changes.outputs.services, 'catalog-service')
        run: |
          dotnet ef database update \
            --project services/catalog-service-net/src/CatalogService.Infrastructure/CatalogService.Infrastructure.csproj \
            --startup-project services/catalog-service-net/src/CatalogService.API/CatalogService.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_CATALOG_DATABASE_URL_PRODUCTION }}

      - name: Run Booking migrations
        if: contains(needs.detect-changes.outputs.services, 'booking-service')
        run: |
          dotnet ef database update \
            --project services/booking-service-net/src/BookingService.Infrastructure/BookingService.Infrastructure.csproj \
            --startup-project services/booking-service-net/src/BookingService.API/BookingService.API.csproj
        env:
          ConnectionStrings__DefaultConnection: ${{ secrets.NEON_BOOKING_DATABASE_URL_PRODUCTION }}

  # =========================================================================
  # EN: Deploy to Kubernetes
  # VI: Trien khai len Kubernetes
  # =========================================================================
  deploy:
    needs: [detect-changes, build-and-push, migrations]
    if: needs.detect-changes.outputs.services != '[]'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Setup kubectl
        uses: azure/setup-kubectl@v3

      - name: Configure kubectl
        run: |
          echo "${{ secrets.KUBECONFIG_PRODUCTION }}" | base64 -d > kubeconfig
          echo "KUBECONFIG=$(pwd)/kubeconfig" >> $GITHUB_ENV

      - name: Apply namespace and config
        run: |
          kubectl apply -f deployments/production/kubernetes/namespace.yaml
          kubectl apply -f deployments/production/kubernetes/configmap.yaml

      - name: Deploy Redis
        run: |
          kubectl apply -f deployments/production/kubernetes/redis.yaml

      # EN: Update image tags to commit SHA and deploy
      # VI: Cap nhat image tag bang commit SHA va deploy
      - name: Deploy services
        run: |
          SERVICES='${{ needs.detect-changes.outputs.services }}'

          declare -A DEPLOY_MAP=(
            ["iam-service"]="iam-service.yaml"
            ["merchant-service"]="merchant-service.yaml"
            ["order-service"]="order-service.yaml"
            ["fnb-engine"]="fnb-engine.yaml"
            ["inventory-service"]="inventory-service.yaml"
            ["wallet-service"]="wallet-service.yaml"
            ["catalog-service"]="catalog-service.yaml"
            ["booking-service"]="booking-service.yaml"
            ["pos-web"]="pos-web.yaml"
          )

          declare -A IMAGE_MAP=(
            ["iam-service"]="goodgo/iam-service-net"
            ["merchant-service"]="goodgo/merchant-service-net"
            ["order-service"]="goodgo/order-service-net"
            ["fnb-engine"]="goodgo/fnb-engine-net"
            ["inventory-service"]="goodgo/inventory-service-net"
            ["wallet-service"]="goodgo/wallet-service-net"
            ["catalog-service"]="goodgo/catalog-service-net"
            ["booking-service"]="goodgo/booking-service-net"
            ["pos-web"]="goodgo/web-client-tpos-net"
          )

          for svc in "${!DEPLOY_MAP[@]}"; do
            if echo "$SERVICES" | grep -q "\"${svc}\""; then
              echo "Deploying ${svc}..."
              # EN: Replace IMAGE_TAG placeholder with commit SHA before applying (never :latest in production)
              # VI: Thay the IMAGE_TAG bang commit SHA truoc khi apply (khong bao gio dung :latest trong production)
              MANIFEST="deployments/production/kubernetes/${DEPLOY_MAP[$svc]}"
              sed "s|IMAGE_TAG|${{ github.sha }}|g" "$MANIFEST" | kubectl apply -f -

              kubectl rollout restart "deployment/${svc}" -n production
            fi
          done

      - name: Apply ingress
        run: |
          kubectl apply -f deployments/production/kubernetes/ingress.yaml

      - name: Wait for rollouts
        run: |
          SERVICES='${{ needs.detect-changes.outputs.services }}'

          declare -A DEPLOY_NAMES=(
            ["iam-service"]="iam-service"
            ["merchant-service"]="merchant-service"
            ["order-service"]="order-service"
            ["fnb-engine"]="fnb-engine"
            ["inventory-service"]="inventory-service"
            ["wallet-service"]="wallet-service"
            ["catalog-service"]="catalog-service"
            ["booking-service"]="booking-service"
            ["pos-web"]="pos-web"
          )

          FAILED=0
          for svc in "${!DEPLOY_NAMES[@]}"; do
            if echo "$SERVICES" | grep -q "\"${svc}\""; then
              echo "Waiting for ${svc}..."
              if ! kubectl rollout status "deployment/${DEPLOY_NAMES[$svc]}" -n production --timeout=180s; then
                echo "FAILED: ${svc} rollout did not complete in time"
                FAILED=$((FAILED + 1))
              fi
            fi
          done

          if [ $FAILED -gt 0 ]; then
            echo "ERROR: ${FAILED} service(s) did not complete rollout"
            kubectl get pods -n production
            exit 1
          fi

      - name: Verify deployment
        run: |
          echo "=== Pods ==="
          kubectl get pods -n production -o wide
          echo ""
          echo "=== Services ==="
          kubectl get svc -n production
          echo ""
          echo "=== HPAs ==="
          kubectl get hpa -n production
          echo ""
          echo "=== Ingress ==="
          kubectl get ingress -n production

      # EN: Post-deployment health check
      # VI: Kiem tra suc khoe sau deploy
      - name: Health check
        run: |
          SERVICES='${{ needs.detect-changes.outputs.services }}'

          declare -A DEPLOY_NAMES=(
            ["iam-service"]="iam-service"
            ["merchant-service"]="merchant-service"
            ["order-service"]="order-service"
            ["fnb-engine"]="fnb-engine"
            ["inventory-service"]="inventory-service"
            ["wallet-service"]="wallet-service"
            ["catalog-service"]="catalog-service"
            ["booking-service"]="booking-service"
          )

          echo "Running post-deployment health checks..."
          for svc in "${!DEPLOY_NAMES[@]}"; do
            if echo "$SERVICES" | grep -q "\"${svc}\""; then
              POD=$(kubectl get pods -n production -l app=${svc} -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
              if [ -n "$POD" ]; then
                HEALTH=$(kubectl exec "$POD" -n production -- curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/health/live 2>/dev/null || echo "000")
                if [ "$HEALTH" = "200" ]; then
                  echo "${svc}: HEALTHY (200)"
                else
                  echo "${svc}: UNHEALTHY (${HEALTH})"
                fi
              fi
            fi
          done
