name: Pester Tests

on:
  workflow_call:
    inputs:
      soft-fail:
        description: 'Whether to continue on test failures'
        required: false
        type: boolean
        default: false
      changed-files-only:
        description: 'Only test changed PowerShell files'
        required: false
        type: boolean
        default: true
      code-coverage:
        description: 'Enable code coverage reporting'
        required: false
        type: boolean
        default: false

permissions:
  contents: read

jobs:
  pester:
    name: PowerShell Tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write  # Required for Codecov OIDC when enable-code-coverage is true
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
        with:
          persist-credentials: false
          fetch-depth: 0

      - name: Install Pester
        shell: pwsh
        run: |
          Install-Module -Name Pester -RequiredVersion 5.7.1 -Force -Scope CurrentUser
          Import-Module Pester -RequiredVersion 5.7.1

      - name: Install PowerShell-Yaml
        shell: pwsh
        run: |
          Install-Module -Name PowerShell-Yaml -Force -Scope CurrentUser

      - name: Get changed PowerShell files
        id: changed-files
        if: inputs.changed-files-only
        shell: pwsh
        run: |
          # Get changed PowerShell files between base and head
          $diffOutput = git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.ps1' '*.psm1' 2>$null
          $changedFiles = @($diffOutput -split "`n" | Where-Object { $_ -and $_.Trim() })
          
          if ($changedFiles.Count -eq 0) {
            Write-Host "No changed PowerShell files found"
            if ($env:GITHUB_ENV) {
              "HAS_CHANGES=false" | Out-File -FilePath $env:GITHUB_ENV -Append
            }
            exit 0
          }
          
          Write-Host "Changed PowerShell files:"
          $changedFiles | ForEach-Object { Write-Host "  - $_" }

          # Build test search paths: scripts/tests + skill test dirs at known depths
          # Skills live at .github/skills/<skill>/ or .github/skills/<collection>/<skill>/
          $testSearchPaths = @('scripts/tests')
          $skillsRoot = Join-Path (Get-Location) '.github' 'skills'
          if (Test-Path $skillsRoot) {
            foreach ($depth in @('*', '*/*')) {
              $pattern = Join-Path $skillsRoot $depth 'tests'
              Get-Item -Path $pattern -ErrorAction SilentlyContinue |
                Where-Object { $_.PSIsContainer -and (Test-Path (Join-Path $_.Parent.FullName 'scripts')) } |
                ForEach-Object { $testSearchPaths += $_.FullName }
            }
          }

          # Map source files to their test files
          $testFiles = @()
          foreach ($file in $changedFiles) {
            $fileName = [System.IO.Path]::GetFileNameWithoutExtension($file)
            # Look for corresponding test file in all test search paths (scripts/tests and skill test dirs)
            $matchingTests = $testSearchPaths | ForEach-Object {
              Get-ChildItem -Path $_ -Filter "$fileName.Tests.ps1" -Recurse -ErrorAction SilentlyContinue
            }
            if ($matchingTests) {
              $testFiles += $matchingTests.FullName
            }
          }

          # Also include any directly changed test files
          $changedTests = @($changedFiles | Where-Object { $_ -like '*.Tests.ps1' })
          foreach ($test in $changedTests) {
            $fullPath = Join-Path (Get-Location) $test
            if (Test-Path $fullPath) {
              $testFiles += $fullPath
            }
          }

          $testFiles = @($testFiles | Select-Object -Unique)
          if ($testFiles.Count -eq 0) {
            Write-Host "No matching test files for changed PowerShell files"
            if ($env:GITHUB_ENV) {
              "HAS_CHANGES=false" | Out-File -FilePath $env:GITHUB_ENV -Append
            }
          } else {
            Write-Host "Found $($testFiles.Count) test file(s) to run:"
            $testFiles | ForEach-Object { Write-Host "  - $_" }
            if ($env:GITHUB_ENV) {
              "HAS_CHANGES=true" | Out-File -FilePath $env:GITHUB_ENV -Append
              "TEST_PATHS=$($testFiles -join ';')" | Out-File -FilePath $env:GITHUB_ENV -Append
            }
          }

      - name: Run Pester Tests
        id: pester
        if: "!inputs.changed-files-only || env.HAS_CHANGES == 'true'"
        shell: pwsh
        run: |
          $params = @{ CI = $true }

          if ('${{ inputs.code-coverage }}' -eq 'true') {
            $params['CodeCoverage'] = $true
          }

          # Use specific test paths if changed-files-only is enabled
          if ('${{ inputs.changed-files-only }}' -eq 'true' -and $env:TEST_PATHS) {
            $testPaths = $env:TEST_PATHS -split ';'
            $params['TestPath'] = $testPaths
          }

          ./scripts/tests/Invoke-PesterTests.ps1 @params

          # Read coverage from summary JSON for downstream steps
          if ('${{ inputs.code-coverage }}' -eq 'true' -and (Test-Path 'logs/pester-summary.json')) {
            $summary = Get-Content 'logs/pester-summary.json' | ConvertFrom-Json
            if ($summary.CoveragePercent) {
              Write-Host "Code coverage: $($summary.CoveragePercent)%"
              if ($env:GITHUB_OUTPUT) {
                $outDir = Split-Path -Parent $env:GITHUB_OUTPUT
                if ($outDir -and -not (Test-Path $outDir)) {
                  New-Item -ItemType Directory -Path $outDir -Force | Out-Null
                }
                if (-not (Test-Path $env:GITHUB_OUTPUT)) {
                  New-Item -ItemType File -Path $env:GITHUB_OUTPUT -Force | Out-Null
                }
                "coverage=$($summary.CoveragePercent)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
              }
            }
          }
        continue-on-error: ${{ inputs.soft-fail }}

      - name: Upload test results
        if: always() && (!inputs.changed-files-only || env.HAS_CHANGES == 'true')
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4.4.3
        with:
          name: pester-test-results
          path: |
            logs/pester-results.xml
            logs/pester-summary.json
            logs/pester-failures.json
          retention-days: 30
          if-no-files-found: ignore

      - name: Upload Coverage Report
        if: inputs.code-coverage && success() && (!inputs.changed-files-only || env.HAS_CHANGES == 'true')
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4.4.3
        with:
          name: coverage-report
          path: logs/coverage.xml
          retention-days: 30
          if-no-files-found: warn

      - name: Upload to Codecov
        if: inputs.code-coverage && success() && (!inputs.changed-files-only || env.HAS_CHANGES == 'true')
        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
        with:
          files: logs/coverage.xml
          use_oidc: true
          fail_ci_if_error: false
          verbose: true
          flags: pester
          name: pester-coverage

      - name: Coverage Threshold Check
        if: inputs.code-coverage && success() && (!inputs.changed-files-only || env.HAS_CHANGES == 'true')
        shell: pwsh
        run: |
          $coverageValue = '${{ steps.pester.outputs.coverage }}'
          if ([string]::IsNullOrWhiteSpace($coverageValue)) {
            Write-Warning "Coverage output is empty - skipping threshold check"
            exit 0
          }
          $percentage = [double]$coverageValue
          $threshold = 18  # Informational baseline

          Write-Host "Coverage: $percentage%"
          Write-Host "Baseline threshold: $threshold%"

          if ($percentage -lt $threshold) {
            Write-Warning "Coverage $percentage% is below baseline $threshold% - informational only"
          } else {
            Write-Host "Coverage meets or exceeds baseline threshold" -ForegroundColor Green
          }

      - name: Check results
        if: "!inputs.soft-fail && (!inputs.changed-files-only || env.HAS_CHANGES == 'true')"
        shell: pwsh
        run: |
          if (Test-Path 'logs/pester-summary.json') {
            $summary = Get-Content 'logs/pester-summary.json' | ConvertFrom-Json
            if ($summary.FailedCount -gt 0) {
              Write-Error "Pester tests failed: $($summary.FailedCount) failure(s)"
              exit 1
            }
          }
