#
# This workflow will build all .slnx files in the dotnet folder, and run all unit tests and integration tests using dotnet docker containers,
# each targeting a single version of the dotnet SDK.
#

name: dotnet-build-and-test

on:
  workflow_dispatch:
  pull_request:
    branches: ["main", "feature*"]
  merge_group:
    branches: ["main", "feature*"]
  push:
    branches: ["main", "feature*"]
  schedule:
    - cron: "0 0 * * *" # Run at midnight UTC daily

env:
  COVERAGE_THRESHOLD: 80

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

permissions:
  contents: read
  id-token: "write"

jobs:
  paths-filter:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
    outputs:
      dotnetChanges: ${{ steps.filter.outputs.dotnet}}
    steps:
      - uses: actions/checkout@v5
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            dotnet:
              - 'dotnet/**'
      # run only if 'dotnet' files were changed
      - name: dotnet tests
        if: steps.filter.outputs.dotnet == 'true'
        run: echo "Dotnet file"
      # run only if not 'dotnet' files were changed
      - name: not dotnet tests
        if: steps.filter.outputs.dotnet != 'true'
        run: echo "NOT dotnet file"

  dotnet-build-and-test:
    needs: paths-filter
    if: needs.paths-filter.outputs.dotnetChanges == 'true'
    strategy:
      fail-fast: false
      matrix:
        include:
          - { targetFramework: "net9.0", os: "ubuntu-latest", configuration: Release, integration-tests: true, environment: "integration" }
          - { targetFramework: "net9.0", os: "ubuntu-latest", configuration: Debug }
          - { targetFramework: "net9.0", os: "windows-latest", configuration: Release }
          - { targetFramework: "net472", os: "windows-latest", configuration: Release, integration-tests: true, environment: "integration" }

    runs-on: ${{ matrix.os }}
    environment: ${{ matrix.environment }}
    steps:
      - uses: actions/checkout@v5
        with:
          persist-credentials: false
          sparse-checkout: |
            .
            .github
            dotnet
            python
            workflow-samples

      - name: Setup dotnet
        uses: actions/setup-dotnet@v5.0.0
        with:
          global-json-file: ${{ github.workspace }}/dotnet/global.json
      - name: Build dotnet solutions
        shell: bash
        run: |
          export SOLUTIONS=$(find ./dotnet/ -type f -name "*.slnx" | tr '\n' ' ')
          for solution in $SOLUTIONS; do
            dotnet build $solution -c ${{ matrix.configuration }} --warnaserror
          done
      - name: Package install check
        shell: bash
        # All frameworks are only built for the release configuration, so we only run this step for the release configuration
        # and dotnet new doesn't support net472
        if: matrix.configuration == 'Release' && matrix.targetFramework != 'net472'
        run: |
          TEMP_DIR=$(mktemp -d)

          export SOLUTIONS=$(find ./dotnet/ -type f -name "*.slnx" | tr '\n' ' ')
          for solution in $SOLUTIONS; do
            dotnet pack $solution /property:TargetFrameworks=${{ matrix.targetFramework }} -c ${{ matrix.configuration }} --no-build --no-restore --output "$TEMP_DIR/artifacts"
          done

          pushd "$TEMP_DIR"

          # Create a new console app to test the package installation
          dotnet new console -f ${{ matrix.targetFramework }} --name packcheck --output consoleapp

          # Create minimal nuget.config and use only dotnet nuget commands
          echo '<?xml version="1.0" encoding="utf-8"?><configuration><packageSources><clear /></packageSources></configuration>' > consoleapp/nuget.config

          # Add sources with local first using dotnet nuget commands
          dotnet nuget add source ../artifacts --name local --configfile consoleapp/nuget.config
          dotnet nuget add source https://api.nuget.org/v3/index.json --name nuget.org --configfile consoleapp/nuget.config

          # Change to project directory to ensure local nuget.config is used
          pushd consoleapp
          dotnet add packcheck.csproj package Microsoft.Agents.AI --prerelease
          dotnet build -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} packcheck.csproj

          # Clean up
          popd
          popd
          rm -rf "$TEMP_DIR"

      - name: Run Unit Tests Windows
        shell: bash
        run: |
          export UT_PROJECTS=$(find ./dotnet -type f -name "*.UnitTests.csproj" | tr '\n' ' ')
          for project in $UT_PROJECTS; do
            # Query the project's target frameworks using MSBuild with the current configuration
            target_frameworks=$(dotnet msbuild $project -getProperty:TargetFrameworks -p:Configuration=${{ matrix.configuration }} -nologo 2>/dev/null | tr -d '\r')
            
            # Check if the project supports the target framework
            if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then
              dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx --collect:"XPlat Code Coverage" --results-directory:"TestResults/Coverage/" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ExcludeByAttribute=GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute
            else
              echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)"
            fi
          done
      
      - name: Log event name and matrix integration-tests
        shell: bash 
        run: echo "github.event_name:${{ github.event_name }} matrix.integration-tests:${{ matrix.integration-tests }} github.event.action:${{ github.event.action }} github.event.pull_request.merged:${{ github.event.pull_request.merged }}" 

      - name: Azure CLI Login
        if: github.event_name != 'pull_request' && matrix.integration-tests
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Run Integration Tests
        shell: bash
        if: github.event_name != 'pull_request' && matrix.integration-tests
        run: |
          export INTEGRATION_TEST_PROJECTS=$(find ./dotnet -type f -name "*IntegrationTests.csproj" | tr '\n' ' ')
          for project in $INTEGRATION_TEST_PROJECTS; do
            # Query the project's target frameworks using MSBuild with the current configuration
            target_frameworks=$(dotnet msbuild $project -getProperty:TargetFrameworks -p:Configuration=${{ matrix.configuration }} -nologo 2>/dev/null | tr -d '\r')
            
            # Check if the project supports the target framework
            if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then
              dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx
            else
              echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)"
            fi
          done
        env:
          # OpenAI Models
          OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
          OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }}
          OpenAI__ChatReasoningModelId: ${{ vars.OPENAI__CHATREASONINGMODELID }}
          # Azure AI Foundry
          AzureAI__Endpoint: ${{ secrets.AZUREAI__ENDPOINT }}
          AzureAI__DeploymentName: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
          AzureAI__BingConnectionId: ${{ vars.AZUREAI__BINGCONECTIONID }}
          FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
          FOUNDRY_MEDIA_DEPLOYMENT_NAME: ${{ vars.FOUNDRY_MEDIA_DEPLOYMENT_NAME }}
          FOUNDRY_MODEL_DEPLOYMENT_NAME: ${{ vars.FOUNDRY_MODEL_DEPLOYMENT_NAME }}
          FOUNDRY_CONNECTION_GROUNDING_TOOL: ${{ vars.FOUNDRY_CONNECTION_GROUNDING_TOOL }}

      # Generate test reports and check coverage
      - name: Generate test reports
        uses: danielpalme/ReportGenerator-GitHub-Action@5.4.18
        with:
          reports: "./TestResults/Coverage/**/coverage.cobertura.xml"
          targetdir: "./TestResults/Reports"
          reporttypes: "HtmlInline;JsonSummary"

      - name: Upload coverage report artifact
        uses: actions/upload-artifact@v5
        with:
          name: CoverageReport-${{ matrix.os }}-${{ matrix.targetFramework }}-${{ matrix.configuration }} # Artifact name
          path: ./TestResults/Reports # Directory containing files to upload

      - name: Check coverage
        shell: pwsh
        run: .github/workflows/dotnet-check-coverage.ps1 -JsonReportPath "TestResults/Reports/Summary.json" -CoverageThreshold $env:COVERAGE_THRESHOLD

  # This final job is required to satisfy the merge queue. It must only run (or succeed) if no tests failed
  dotnet-build-and-test-check:
    if: always()
    runs-on: ubuntu-latest
    needs: [dotnet-build-and-test]
    steps:
      - name: Get Date
        shell: bash
        run: |
          echo "date=$(date +'%m/%d/%Y %H:%M:%S')" >> "$GITHUB_ENV"

      - name: Run Type is Daily
        if: ${{ github.event_name == 'schedule' }}
        shell: bash
        run: |
          echo "run_type=Daily" >> "$GITHUB_ENV"

      - name: Run Type is Manual
        if: ${{ github.event_name == 'workflow_dispatch' }}
        shell: bash
        run: |
          echo "run_type=Manual" >> "$GITHUB_ENV"

      - name: Run Type is ${{ github.event_name }}
        if: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'}}
        shell: bash
        run: |
          echo "run_type=${{ github.event_name }}" >> "$GITHUB_ENV"

      - name: Fail workflow if tests failed
        id: check_tests_failed
        if: contains(join(needs.*.result, ','), 'failure')
        uses: actions/github-script@v8
        with:
          script: core.setFailed('Integration Tests Failed!')

      - name: Fail workflow if tests cancelled
        id: check_tests_cancelled
        if: contains(join(needs.*.result, ','), 'cancelled')
        uses: actions/github-script@v8
        with:
          script: core.setFailed('Integration Tests Cancelled!')
