# =============================================================================
# .gitlab-ci.yml — CI/CD Pipeline with Agentic QA Test Generation
# =============================================================================
#
# WHAT THIS PIPELINE DOES:
#
#   1. Developer pushes code to a feature branch, opens a Merge Request
#   2. Pipeline triggers automatically on MR creation or push to main
#   3. [build]     Gradle compiles the Spring Boot application
#   4. [claude-qa] Claude Code agent:
#      a. Reads REQUIREMENTS.md (business requirements written by humans)
#      b. Reads CLAUDE.md (test scaffolding docs and Karate patterns)
#      c. Scans source code in src/main/ to understand the implementation
#      d. DISCOVERS edge cases by analyzing code paths and boundary conditions
#      e. GENERATES Karate .feature files from scratch
#      f. GENERATES TEST-CASES.md listing every test with its source
#      g. Runs tests against real PostgreSQL (Testcontainers) + real HTTP
#      h. Fixes any failures and re-runs (self-correction loop)
#      i. Commits generated files back to the feature branch
#      j. Posts a structured pass/fail report as an MR comment
#   5. [deploy]    Manual deploy gate (only on main branch)
#
# WHAT THE DEVELOPER SEES IN THE MR:
#
#   - Pipeline badge: green (all passed) or red (failures)
#   - Diff tab: generated .feature files and TEST-CASES.md (reviewable!)
#   - Test tab: JUnit XML showing pass/fail per Karate scenario
#   - Comment: agent's report with requirement coverage + discovered edge cases
#   - Developer reviews everything, approves, and merges
#
# REQUIRED CI/CD VARIABLES (Settings → CI/CD → Variables):
#
#   ANTHROPIC_API_KEY  — Anthropic API key
#                        Scope: All environments | Masked: Yes | Protected: Yes
#                        This authenticates the Claude Code CLI with Anthropic's API.
#                        Alternatively, use AWS Bedrock or Google Vertex AI vars.
#
#   GITLAB_TOKEN       — Project access token with write_repository + api scope
#                        Scope: All environments | Masked: Yes | Protected: Yes
#                        Needed for TWO things:
#                          1. git push — committing generated tests back to the branch
#                          2. GitLab API — posting the agent's report as an MR comment
#                        Create at: Settings → Access Tokens → Add new token
#                        Required scopes: write_repository, api
#
# =============================================================================

stages:
  - build
  - claude-qa
  - deploy

default:
  cache:
    key: "${CI_COMMIT_REF_SLUG}"
    paths: [.gradle/, build/]

variables:
  GRADLE_OPTS: "-Dorg.gradle.daemon=false"
  GRADLE_USER_HOME: "${CI_PROJECT_DIR}/.gradle"


# =============================================================================
# STAGE 1: BUILD
# =============================================================================
# What it does:  Compiles the Spring Boot application using Gradle
# Image:         eclipse-temurin:21-jdk (standard Java 21 build image)
# Output:        build/libs/*.jar (passed as artifact to next stage)
# When it runs:  On every MR event or push to main
# =============================================================================
build:
  stage: build
  image: eclipse-temurin:21-jdk
  script:
    - chmod +x gradlew
    - ./gradlew clean assemble --no-daemon
  artifacts:
    paths: [build/libs/*.jar]
    expire_in: 1 hour
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH


# =============================================================================
# STAGE 2: CLAUDE CODE QA AGENT
# =============================================================================
# What it does:  Runs an AI agent that generates tests from requirements,
#                discovers edge cases from source code, executes them against
#                a real database, and commits results back to the branch.
#
# Image:         node:20 (Claude Code CLI is an npm package)
# Extra tools:   JDK 21 (installed in before_script for running Gradle/tests)
#                git (for committing generated files back to the branch)
#
# Database:      No service container needed here. Testcontainers handles it.
#                When Karate tests run via Gradle, Testcontainers automatically
#                starts a PostgreSQL 16 Docker container, runs Flyway migrations,
#                and tears it down after tests complete.
#
# What happens inside this stage (step by step):
#
#   ┌────────────────────────────────────────────────────────────────────┐
#   │  CI Runner (Docker container: node:20)                            │
#   │                                                                    │
#   │  before_script:                                                    │
#   │    [1] Install JDK 21 from Eclipse Adoptium                       │
#   │        → Agent needs javac/java to compile and run Karate tests   │
#   │    [2] Install Claude Code CLI via npm                            │
#   │        → The CLI provides: claude --print --allowedTools ...      │
#   │    [3] Configure git credentials using GITLAB_TOKEN               │
#   │        → So the agent can push generated files back to the branch │
#   │    [4] Checkout the feature branch                                │
#   │        → Pipeline runs in detached HEAD by default; we need       │
#   │          the actual branch to push commits to                     │
#   │                                                                    │
#   │  script:                                                           │
#   │    [5] Invoke the QA agent defined in:                              │
#   │        .claude/agents/qa-agent.md                                   │
#   │        This MD file contains: YAML frontmatter (tools, model,       │
#   │        maxTurns) + the full 7-step agent prompt. The CI/CD just     │
#   │        tells Claude to run it. All logic lives in the agent file.   │
#   │        → Agent reads requirements + code → discovers edge cases     │
#   │        → Generates .feature files + TEST-CASES.md                   │
#   │        → Runs ./gradlew test → fixes failures → re-runs            │
#   │    [6] git add + commit + push generated files                    │
#   │        → .feature files and TEST-CASES.md appear in the MR diff  │
#   │    [7] Post structured report as MR comment via GitLab API        │
#   │        → Developer sees pass/fail + edge case table in the MR    │
#   └────────────────────────────────────────────────────────────────────┘
#
# =============================================================================
claude-qa:
  stage: claude-qa
  # Official docs use node:24-alpine. We need a Debian-based image
  # because Testcontainers needs Docker socket access and JDK install.
  image: node:20

  variables:
    # Claude Code CLI detects this env var automatically.
    # No interactive login, no browser OAuth, no /login command needed.
    # The CLI sees ANTHROPIC_API_KEY and uses it directly for API calls.
    # Set this in: Settings → CI/CD → Variables (masked + protected).
    ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY}"

  before_script:
    # ── [1] Install JDK 21 ──
    # Claude Code itself doesn't need Java. But the agent runs
    # ./gradlew test, which compiles Karate tests and starts Spring Boot.
    # Testcontainers (inside the Gradle test) then starts PostgreSQL.
    - apt-get update -qq && apt-get install -y -qq wget git > /dev/null
    - wget -q https://download.eclipse.org/adoptium/21/latest/jdk/x64/linux/OpenJDK21U-jdk_x64_linux_hotspot.tar.gz -O /tmp/jdk.tar.gz
    - mkdir -p /opt/java && tar xzf /tmp/jdk.tar.gz -C /opt/java --strip-components=1
    - export JAVA_HOME=/opt/java && export PATH=$JAVA_HOME/bin:$PATH
    - java --version

    # ── [2] Install Claude Code CLI ──
    # This installs the 'claude' command globally. ~45MB.
    # The CLI is a Node.js tool that talks to the Anthropic API.
    # It runs HERE in the CI runner container, NOT inside your app.
    # Authentication happens automatically via ANTHROPIC_API_KEY env var.
    - npm install -g @anthropic-ai/claude-code

    # ── [3] Configure git for pushing generated files ──
    - git config user.email "claude-qa-agent@ci.local"
    - git config user.name "Claude QA Agent"
    - git remote set-url origin "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"

    # ── [4] Checkout the actual branch ──
    - git fetch origin "${CI_COMMIT_REF_NAME}"
    - git checkout "${CI_COMMIT_REF_NAME}"

  script:
    - export JAVA_HOME=/opt/java && export PATH=$JAVA_HOME/bin:$PATH
    - chmod +x gradlew

    # ── [5] Run the Claude Code QA Agent ──
    #
    # HOW AUTHENTICATION WORKS:
    #   Claude Code CLI checks for ANTHROPIC_API_KEY env var.
    #   If present, it uses it directly for API calls. No browser
    #   login, no OAuth flow, no interactive prompts. This is how
    #   headless/CI mode works.
    #
    # HOW THE AGENT RUNS:
    #   The -p flag = "print mode" (headless, non-interactive).
    #   Claude Code loads .claude/agents/qa-agent.md at startup.
    #   The agent definition contains: tools, model, maxTurns,
    #   and the full 7-step prompt.
    #
    #   --permission-mode acceptEdits: auto-accepts file edits
    #     without prompting (required in CI, no human to click "yes")
    #
    #   The prompt just tells Claude to follow the qa-agent workflow.
    #   All the intelligence (what to read, what to discover, how to
    #   generate tests) is in .claude/agents/qa-agent.md.
    #
    - |
      claude -p \
        --permission-mode acceptEdits \
        --allowedTools "Bash(./gradlew*) Bash(find*) Bash(cat*) Bash(ls*) Bash(grep*) Read Write Edit" \
        "Run the qa-agent workflow. Follow every step defined in the
      qa-agent agent definition (.claude/agents/qa-agent.md).
      Read REQUIREMENTS.md for business requirements, analyze the
      source code to discover edge cases, generate Karate test
      scenarios and TEST-CASES.md, run the tests, fix any failures,
      and output the structured report between ---REPORT-START---
      and ---REPORT-END--- markers." \
        2>&1 | tee claude-qa-report.txt

    # ── [6] Commit generated files back to the branch ──
    #
    # After the agent generates and verifies tests, we commit THREE types
    # of files to the feature branch:
    #
    #   .feature files     → The executable Karate test scenarios
    #   TEST-CASES.md      → Human-readable test plan with edge case explanations
    #
    # These appear in the MR diff. The developer reads TEST-CASES.md for
    # the overview, then inspects .feature files for implementation details.
    #
    # [skip ci] prevents the push from triggering another pipeline run.
    #
    - |
      FEATURES=$(find src/test/resources/karate/features -name "*.feature" 2>/dev/null | wc -l)
      TESTCASES=$(find src/test/resources/karate -name "TEST-CASES.md" 2>/dev/null | wc -l)

      if [ "$FEATURES" -gt 0 ] || [ "$TESTCASES" -gt 0 ]; then
        git add src/test/resources/karate/features/*.feature 2>/dev/null || true
        git add src/test/resources/karate/TEST-CASES.md 2>/dev/null || true
        git commit -m "🤖 Claude QA Agent: generated ${FEATURES} Karate feature file(s) + test plan

      Auto-generated from REQUIREMENTS.md + source code analysis.
      Edge cases were discovered by the agent, not specified in requirements.
      Review the generated tests and TEST-CASES.md, then merge if correct.

      [skip ci]" || echo "Nothing to commit"
        git push origin "${CI_COMMIT_REF_NAME}" || echo "Push failed (branch may be protected)"
        echo "✅ Committed ${FEATURES} feature file(s) + test plan to branch"
      else
        echo "⚠️ No files were generated"
      fi

    # ── [7] Post results to the Merge Request ──
    #
    # We extract the structured report from the agent's output (between
    # the ---REPORT-START--- and ---REPORT-END--- markers) and post it
    # as a comment on the MR via the GitLab API.
    #
    # The developer sees this WITHOUT opening pipeline logs:
    #   - Which scenarios passed/failed
    #   - Which requirements are covered
    #   - Which edge cases the agent discovered and why
    #   - Any bugs or issues found
    #
    # GITLAB_TOKEN must have 'api' scope for this to work.
    #
    - |
      if [ -n "$CI_MERGE_REQUEST_IID" ] && [ -n "$GITLAB_TOKEN" ]; then
        REPORT=$(sed -n '/---REPORT-START---/,/---REPORT-END---/p' claude-qa-report.txt | \
                 grep -v "REPORT-START\|REPORT-END" || \
                 tail -60 claude-qa-report.txt)

        curl --silent --request POST \
          --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
          --header "Content-Type: application/json" \
          --data "$(jq -n \
            --arg body "## 🤖 Claude QA Agent Report

Generated Karate test scenarios from business requirements (REQUIREMENTS.md) and autonomously discovered edge cases by analyzing the source code. Tests executed against real PostgreSQL (Testcontainers).

$REPORT

---
*Generated tests and TEST-CASES.md have been committed to this branch.*
*Review the files in the Diff tab above. Edge cases marked [EDGE CASE] were discovered by the agent.*" \
            '{body: $body}')" \
          "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes" \
          > /dev/null

        echo "✅ Report posted to MR #${CI_MERGE_REQUEST_IID}"
      fi

  # ── Artifacts ──
  # These files are preserved after the job finishes:
  #
  #   claude-qa-report.txt           → Full agent output (for debugging)
  #   build/karate-reports/          → Karate's HTML report with screenshots
  #   build/reports/                 → JaCoCo coverage + test reports
  #   src/test/resources/karate/     → Generated .feature files + TEST-CASES.md
  #
  #   junit: build/test-results/...  → GitLab parses this and shows
  #                                    pass/fail per scenario in the MR Test tab
  #
  artifacts:
    when: always
    paths:
      - claude-qa-report.txt
      - build/karate-reports/
      - build/reports/
      - src/test/resources/karate/
    reports:
      junit: build/test-results/test/*.xml
    expire_in: 7 days

  # Start as non-blocking while tuning the agent prompt.
  # Remove allow_failure once you're confident in the output quality
  # to make this stage a required gate for merging.
  allow_failure: true

  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH


# =============================================================================
# STAGE 3: DEPLOY
# =============================================================================
# What it does:  Deploys the application (customize for your target)
# When it runs:  Manual trigger, only on main branch
# =============================================================================
deploy:
  stage: deploy
  image: google/cloud-sdk:latest
  script:
    - echo "Deploy step — customize for Cloud Run, K8s, ECS, etc."
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
