name: CI

permissions:
  contents: read

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version-file: "go.mod"

      - name: Cache Go modules
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }}-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-

      - name: Install libpcap
        run: sudo apt-get install -y libpcap-dev

      - name: Download dependencies
        run: go mod download

      - name: Verify dependencies
        run: go mod verify

      - name: Run tests
        run: go test -v -race -coverprofile=coverage.out ./...

      - name: Build service
        run: make build-service

      - name: Run HTTP client integration tests
        run: make test-http-client

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
        with:
          file: ./coverage.out
          flags: unittests
          name: codecov-umbrella
          fail_ci_if_error: false

  lint:
    name: Lint
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version-file: "go.mod"

      - name: Install libpcap
        run: sudo apt-get install -y libpcap-dev

      - name: Run golangci-lint
        uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1
        with:
          version: latest
          args: --timeout=5m

  build:
    name: Build
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - goos: linux
            goarch: amd64
          - goos: linux
            goarch: arm64
          - goos: linux
            goarch: arm
            goarm: 7
          - goos: darwin
            goarch: amd64
          - goos: darwin
            goarch: arm64
          - goos: windows
            goarch: amd64
          - goos: freebsd
            goarch: amd64

    steps:
      - name: Checkout code
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version-file: "go.mod"

      - name: Cache Go modules
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }}-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-

      - name: Build binaries
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          GOARM: ${{ matrix.goarm }}
          CGO_ENABLED: 0
        run: |
          ARCH_SUFFIX="${{ matrix.goos }}-${{ matrix.goarch }}"
          if [[ -n "${{ matrix.goarm }}" ]]; then
            ARCH_SUFFIX="${ARCH_SUFFIX}v${{ matrix.goarm }}"
          fi

          EXT=""
          if [[ "${{ matrix.goos }}" == "windows" ]]; then
            EXT=".exe"
          fi

          mkdir -p build

          for binary in soundtouch-cli soundtouch-service soundtouch-web soundtouch-backup; do
            OUTPUT="build/${binary}-${ARCH_SUFFIX}${EXT}"
            echo "Building $OUTPUT"
            go build -trimpath -ldflags="-s -w" -o "$OUTPUT" "./cmd/$binary"
          done

          ls -la build/

      - name: Upload build artifacts
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm }}
          path: build/

  security:
    name: Basic Security Check
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version-file: "go.mod"

      - name: Install libpcap
        run: sudo apt-get install -y libpcap-dev

      - name: Run basic vulnerability check
        run: |
          go install golang.org/x/vuln/cmd/govulncheck@latest
          govulncheck ./...

      - name: Security scan reminder
        run: |
          echo "ℹ️ This is a basic security check for CI speed."
          echo "For comprehensive security scanning, see the Security workflow:"
          echo "https://github.com/${{ github.repository }}/actions/workflows/security.yml"

  docs:
    name: Documentation Check
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Check documentation links
        run: |
          npm install -g markdown-link-check
          find . -name "*.md" -not -path "./tests/*" -not -path "./node_modules/*" -print0 | xargs -0 -n1 markdown-link-check -q -v -c .github/markdown-link-check.json

      - name: Warn on pending images
        run: |
          IMAGES=(
            "dashboard-home.png"
            "account-creation.png"
            "account-dashboard.png"
            "usb-remote-services.png"
            "device-discovery.png"
            "device-registration.png"
            "account-migration.png"
            "migration-setup.png"
            "migration-progress.png"
            "migration-health.png"
            "migration-complete.png"
            "backup-setup.png"
          )

          for img in "${IMAGES[@]}"; do
            if [ ! -f "docs/static/images/$img" ]; then
              echo "::warning file=docs/content/docs/guides/MIGRATION-GUIDE.md::Pending image '$img' is missing from docs/static/images/"
            fi
          done

      - name: Validate API documentation
        run: |
          # Check that all documented endpoints exist in code
          echo "Validating API documentation consistency..."

          # Check API cookbook
          if [ -f "docs/content/docs/reference/API-COOKBOOK.md" ]; then
            echo "✓ API Cookbook exists"
          else
            echo "✗ API Cookbook missing"
            exit 1
          fi

          # Check getting started guide
          if [ -f "docs/content/docs/guides/GETTING-STARTED.md" ]; then
            echo "✓ Getting Started guide exists"
          else
            echo "✗ Getting Started guide missing"
            exit 1
          fi

  integration:
    name: Integration Test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    steps:
      - name: Checkout code
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up Go
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version-file: "go.mod"

      - name: Test CLI build and help
        run: |
          go build -trimpath -ldflags="-s -w" -o soundtouch-cli ./cmd/soundtouch-cli
          ./soundtouch-cli -help

      - name: Test library imports
        run: |
          cat > test_import.go << 'EOF'
          package main

          import (
              "fmt"
              "github.com/gesellix/bose-soundtouch/pkg/client"
              "github.com/gesellix/bose-soundtouch/pkg/models"
              "github.com/gesellix/bose-soundtouch/pkg/discovery"
              "github.com/gesellix/bose-soundtouch/pkg/config"
          )

          func main() {
              // Test basic client creation
              c := client.NewClientFromHost("192.0.2.100")
              fmt.Printf("Client created for %s\n", c.BaseURL())

              // Test models can be imported
              var info models.DeviceInfo
              fmt.Printf("DeviceInfo model available: %T\n", info)

              // Test discovery can be imported
              cfg := config.DefaultConfig()
              service := discovery.NewUnifiedDiscoveryService(cfg)
              fmt.Printf("Discovery service available: %T\n", service)

              fmt.Println("All imports successful!")
          }
          EOF

          go run test_import.go
          rm test_import.go

  docker:
    name: Docker Build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

      - name: Set build date
        id: build_date
        run: echo "date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT

      - name: Determine push eligibility
        id: push-check
        run: |
          # Push on main, and on same-repo PRs (forks can't push to GHCR via GITHUB_TOKEN).
          SHOULD_PUSH="false"
          if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
            SHOULD_PUSH="true"
          elif [[ "${{ github.event_name }}" == "pull_request" && \
                  "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.repository }}" ]]; then
            SHOULD_PUSH="true"
          fi
          echo "should-push=$SHOULD_PUSH" >> "$GITHUB_OUTPUT"
          echo "Will push: $SHOULD_PUSH"

      - name: Log in to GitHub Container Registry
        if: steps.push-check.outputs.should-push == 'true'
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for soundtouch-service
        id: meta-service
        uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=edge,enable=${{ github.ref == 'refs/heads/main' }}
            type=ref,event=pr,prefix=preview-pr-
            type=sha,prefix=preview-sha-,format=short,enable=${{ github.event_name == 'pull_request' }}
            type=ref,event=branch,prefix=preview-branch-,enable=${{ github.event_name == 'push' && github.ref != 'refs/heads/main' }}

      - name: Build and push soundtouch-service Docker image
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        with:
          context: .
          target: soundtouch-service
          platforms: linux/amd64,linux/arm64,linux/arm64/v8,linux/arm/v7
          push: ${{ steps.push-check.outputs.should-push == 'true' }}
          tags: ${{ steps.meta-service.outputs.tags }}
          labels: ${{ steps.meta-service.outputs.labels }}
          build-args: |
            COMMIT=${{ github.sha }}
            DATE=${{ steps.build_date.outputs.date }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Extract metadata (tags, labels) for soundtouch-web
        id: meta-web
        uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
        with:
          images: ghcr.io/${{ github.repository }}-web
          tags: |
            type=raw,value=edge,enable=${{ github.ref == 'refs/heads/main' }}
            type=ref,event=pr,prefix=preview-pr-
            type=sha,prefix=preview-sha-,format=short,enable=${{ github.event_name == 'pull_request' }}
            type=ref,event=branch,prefix=preview-branch-,enable=${{ github.event_name == 'push' && github.ref != 'refs/heads/main' }}

      - name: Build and push soundtouch-web Docker image
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        with:
          context: .
          target: soundtouch-web
          platforms: linux/amd64,linux/arm64,linux/arm64/v8,linux/arm/v7
          push: ${{ steps.push-check.outputs.should-push == 'true' }}
          tags: ${{ steps.meta-web.outputs.tags }}
          labels: ${{ steps.meta-web.outputs.labels }}
          build-args: |
            COMMIT=${{ github.sha }}
            DATE=${{ steps.build_date.outputs.date }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Summarize published images
        if: steps.push-check.outputs.should-push == 'true'
        env:
          SERVICE_TAGS: ${{ steps.meta-service.outputs.tags }}
          WEB_TAGS: ${{ steps.meta-web.outputs.tags }}
          EVENT_NAME: ${{ github.event_name }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REF_NAME: ${{ github.ref_name }}
        run: |
          {
            echo "## 🐳 Published Docker Images"
            echo ""
            if [[ "$EVENT_NAME" == "pull_request" ]]; then
              echo "**Preview** images for PR #${PR_NUMBER}. These are not release builds."
            elif [[ "$REF_NAME" == "main" ]]; then
              echo "**Edge** images from \`main\`."
            else
              echo "**Preview** images from branch \`${REF_NAME}\`. These are not release builds."
            fi
            echo ""
            echo "### soundtouch-service"
            echo ""
            echo '```bash'
            while IFS= read -r tag; do
              [[ -n "$tag" ]] && echo "docker pull $tag"
            done <<< "$SERVICE_TAGS"
            echo '```'
            echo ""
            echo "### soundtouch-web"
            echo ""
            echo '```bash'
            while IFS= read -r tag; do
              [[ -n "$tag" ]] && echo "docker pull $tag"
            done <<< "$WEB_TAGS"
            echo '```'
          } >> "$GITHUB_STEP_SUMMARY"

  notify:
    name: Notify Status
    runs-on: ubuntu-latest
    needs: [test, lint, build, security, docs, docker]
    if: always()
    permissions:
      statuses: write
      contents: read

    steps:
      - name: Check overall status
        run: |
          if [[ "${{ needs.test.result }}" == "success" && \
                "${{ needs.lint.result }}" == "success" && \
                "${{ needs.build.result }}" == "success" && \
                "${{ needs.security.result }}" == "success" && \
                "${{ needs.docs.result }}" == "success" && \
                "${{ needs.docker.result }}" == "success" ]]; then
            echo "✅ All CI checks passed!"
            echo "status=success" >> $GITHUB_OUTPUT
          else
            echo "❌ Some CI checks failed"
            echo "Test: ${{ needs.test.result }}"
            echo "Lint: ${{ needs.lint.result }}"
            echo "Build: ${{ needs.build.result }}"
            echo "Security: ${{ needs.security.result }}"
            echo "Docs: ${{ needs.docs.result }}"
            echo "Docker: ${{ needs.docker.result }}"
            echo "status=failure" >> $GITHUB_OUTPUT
          fi
        id: status

      - name: Update commit status
        if: always()
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
        with:
          script: |
            try {
              const state = '${{ steps.status.outputs.status }}' === 'success' ? 'success' : 'failure';
              const description = state === 'success' ? 'All checks passed' : 'Some checks failed';

              await github.rest.repos.createCommitStatus({
                owner: context.repo.owner,
                repo: context.repo.repo,
                sha: context.sha,
                state: state,
                description: description,
                context: 'CI Pipeline'
              });

              console.log(`✅ Successfully updated commit status to: ${state}`);
            } catch (error) {
              console.log(`⚠️  Failed to update commit status: ${error.message}`);
              // Don't fail the workflow if status update fails
            }
