name: AI Code Review

# Copilot が作成した PR を Claude Opus で自動レビュー
#
# APPROVE されたものだけ auto-merge.yml がマージする。
# Claude Code CLI（claude --print）+ OAuth トークンローテーションで動作。
# タスク1: CRITICAL 深刻度分岐を実装（WARNING のみ → APPROVE + コメント）
# タスク2 (B4): DESIGN_CONSTRAINTS.md 読み込みステップを追加
# タスク4 (B2): auto-fix — @copilot 自動修正コメント投稿を追加

on:
  pull_request:
    types: [opened, synchronize, ready_for_review]
    branches: [master, main]

jobs:
  ai-review:
    name: Claude Opus Review
    runs-on: ubuntu-latest
    if: |
      github.event.pull_request.user.login == 'Copilot' ||
      contains(github.event.pull_request.labels.*.name, 'ai-review')
    permissions:
      pull-requests: write
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install claude CLI
        run: npm install -g @anthropic-ai/claude-code@latest

      - name: Load Design Constraints
        id: constraints
        run: |
          if [ -f DESIGN_CONSTRAINTS.md ]; then
            CONSTRAINTS=$(cat DESIGN_CONSTRAINTS.md | head -100)
          else
            CONSTRAINTS="No design constraints found."
          fi
          echo "CONSTRAINTS<<EOFDC" >> $GITHUB_OUTPUT
          echo "$CONSTRAINTS" >> $GITHUB_OUTPUT
          echo "EOFDC" >> $GITHUB_OUTPUT

      - name: Get PR diff
        id: diff
        run: |
          git fetch origin master 2>/dev/null || git fetch origin main 2>/dev/null || true
          BASE=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||' || echo "master")
          git diff origin/${BASE}...HEAD > /tmp/pr.diff
          BYTES=$(wc -c < /tmp/pr.diff)
          echo "Diff size: ${BYTES} bytes"

          # ドキュメント専用 PR 判定（全ファイルが .md なら documentation-only）
          CHANGED=$(git diff --name-only origin/${BASE}...HEAD)
          echo "Changed files:"
          echo "$CHANGED"
          if [ -z "$CHANGED" ]; then
            echo "doc_only=false" >> $GITHUB_OUTPUT
          else
          NON_MD=$(echo "$CHANGED" | grep -v '\.md$' | head -1)
            if [ -z "$NON_MD" ]; then
              echo "doc_only=true" >> $GITHUB_OUTPUT
              echo "WARNING: PR contains only .md files — will auto-reject."
            else
              echo "doc_only=false" >> $GITHUB_OUTPUT
            fi
          fi

          # 18000文字に制限（Opus のコンテキスト節約）
          python3 -c "
          with open('/tmp/pr.diff') as f:
              diff = f.read()
          MAX = 18000
          truncated = len(diff) > MAX
          if truncated:
             diff = diff[:MAX] + '\n...[diff truncated due to length]'
          with open('/tmp/pr.diff.limited', 'w') as f:
              f.write(diff)
          print('truncated=true' if truncated else 'truncated=false')
          " >> $GITHUB_ENV

      - name: Reject documentation-only PR
        if: steps.diff.outputs.doc_only == 'true'
        uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            await github.rest.pulls.createReview({
              owner:       context.repo.owner,
              repo:        context.repo.repo,
              pull_number: pr.number,
              event:       'REQUEST_CHANGES',
              body: [
                '## AI Code Review — Documentation-Only PR Rejected',
                '',
                '**[REQUEST_CHANGES]**',
                '',
                'This PR contains **only `.md` documentation files** and no executable code.',
                '',
                'Copilot Coding Agent must produce runnable code files (`.js`, `.ts`, `.py`, `.sh`, etc.).',
                'Please check the Issue requirements and ensure the implementation includes actual code.',
                '',
                '### Required Changes',
                '1. Add at least one executable code file that fulfills the Issue acceptance criteria.',
                '2. Documentation files (`.md`) are optional — code is required.',
              ].join('\n'),
            });
            core.setFailed('Documentation-only PR auto-rejected.');

      - name: Run AI Review
        id: review
        if: steps.diff.outputs.doc_only != 'true'
        env:
          CLAUDE_CODE_OAUTH_TOKEN_1: ${{ secrets.CLAUDE_CODE_TOKEN }}
          CLAUDE_CODE_OAUTH_TOKEN_2: ${{ secrets.CLAUDE_CODE_TOKEN_2 }}
          CLAUDE_CODE_OAUTH_TOKEN_3: ${{ secrets.CLAUDE_CODE_TOKEN_3 }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_TITLE:  ${{ github.event.pull_request.title }}
          PR_BRANCH: ${{ github.event.pull_request.head.ref }}
        run: |
          set -euo pipefail

          SYSTEM_PROMPT="You are a principal engineer performing an automated code review of a GitHub Pull Request created by Copilot Coding Agent. Evaluate the PR for: (1) correctness — does it fulfill the linked Issue requirements? (2) code quality — no dead code, no placeholder implementations; (3) security — no hardcoded secrets, proper input validation; (4) tests — new logic should have tests. Be strict. Only APPROVE if the PR is production-ready and contains real executable code. If the PR contains only .md documentation files with no code, immediately REQUEST_CHANGES. PROJECT DESIGN CONSTRAINTS (you must follow these): ${{ steps.constraints.outputs.CONSTRAINTS }}"

          USER_PROMPT="PR #${PR_NUMBER}: ${PR_TITLE}
          Branch: ${PR_BRANCH}

          Review the diff below. Output MUST follow this exact format:

          ## Verdict
          **[APPROVE]** <- write exactly this if production-ready
          OR
          **[REQUEST_CHANGES]** <- write exactly this if issues found

          ## Summary
          (2-3 sentences, plain language)

          ## Findings
          (use: OK good  WARNING warning  CRITICAL critical)

          ## Required Changes
          (numbered list if REQUEST_CHANGES, else: None)

          Be strict. Only APPROVE if safe and correct."

          # .claude/ ディレクトリの影響を避けるため /tmp から実行
          cd /tmp

          # 3トークンをローテーションして試みる
          USED_KEY=0
          for i in 1 2 3; do
            case $i in
              1) TOKEN="$CLAUDE_CODE_OAUTH_TOKEN_1" ;;
              2) TOKEN="$CLAUDE_CODE_OAUTH_TOKEN_2" ;;
              3) TOKEN="$CLAUDE_CODE_OAUTH_TOKEN_3" ;;
            esac

            if [ -z "${TOKEN:-}" ]; then
              echo "Token $i is empty, skipping."
              continue
            fi

            echo "Trying token $i/3..."
            set +e
            REVIEW=$(cat /tmp/pr.diff.limited | \
              ANTHROPIC_API_KEY="" \
              CLAUDE_CODE_OAUTH_TOKEN="$TOKEN" \
              CLAUDE_NO_ANALYTICS=1 \
              NO_UPDATE_NOTIFIER=1 \
              claude \
                --model claude-opus-4-6 \
                --print "$USER_PROMPT" \
                --system-prompt "$SYSTEM_PROMPT" \
                --no-session-persistence \
                2>/tmp/claude_stderr.txt)
            EXIT_CODE=$?
            set -e

            if [ $EXIT_CODE -eq 0 ] && echo "$REVIEW" | grep -qE '\*\*\[(APPROVE|REQUEST_CHANGES)\]\*\*'; then
              echo "Success with token $i"
              USED_KEY=$i
              break
            else
              echo "Token $i failed (exit: $EXIT_CODE):"
              head -5 /tmp/claude_stderr.txt 2>/dev/null || true
              if grep -q "429\|rate.limit\|overloaded" /tmp/claude_stderr.txt 2>/dev/null; then
                echo "Rate limited, waiting 10s before next token..."
                sleep 10
              fi
            fi
          done

          if [ "$USED_KEY" -eq 0 ]; then
            echo "All tokens failed. Cannot post AI review."
            exit 1
          fi

          echo "$REVIEW" > /tmp/review_output.txt
          echo "used_key=$USED_KEY" >> $GITHUB_OUTPUT

          # タスク1: CRITICAL キーワード検出
          CRITICAL_KEYWORDS="CRITICAL|security|auth|injection|SQL injection|authentication|authorization|vulnerability"
          if echo "$REVIEW" | grep -qiE "$CRITICAL_KEYWORDS"; then
            echo "severity=CRITICAL" >> $GITHUB_OUTPUT
            echo "CRITICAL issue detected in review"
          else
            echo "severity=WARNING" >> $GITHUB_OUTPUT
            echo "Only WARNING-level issues detected"
          fi

          if echo "$REVIEW" | grep -q '\*\*\[APPROVE\]\*\*'; then
            echo "verdict=APPROVE" >> $GITHUB_OUTPUT
          else
            echo "verdict=REQUEST_CHANGES" >> $GITHUB_OUTPUT
          fi

          echo "Verdict: $(grep -o '\*\*\[.*\]\*\*' /tmp/review_output.txt | head -1)"

      - name: Post review to PR
        if: steps.review.outcome == 'success'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const review = fs.readFileSync('/tmp/review_output.txt', 'utf8');
            const verdict = '${{ steps.review.outputs.verdict }}';
            const severity = '${{ steps.review.outputs.severity }}';
            const usedKey = '${{ steps.review.outputs.used_key }}';
            const truncated = process.env.truncated === 'true';
            const pr = context.payload.pull_request;

            // タスク1: WARNING のみの場合は APPROVE に変換
            let event = verdict === 'APPROVE' ? 'APPROVE' : 'REQUEST_CHANGES';
            if (verdict === 'REQUEST_CHANGES' && severity === 'WARNING') {
              event = 'APPROVE';
              console.log('Severity is WARNING-only, converting REQUEST_CHANGES to APPROVE with comments');
            }

            const body = [
              '## AI Code Review -- Claude Opus 4.6 (via Claude Code CLI)',
              '',
              severity === 'WARNING' && verdict === 'REQUEST_CHANGES' 
                ? '**Status**: ✅ APPROVED (WARNING-level issues only — non-blocking)\n'
                : '',
              review,
              '',
              '---',
              `_Model: claude-opus-4-6 | Diff: ${truncated ? 'truncated' : 'full'} | PR: #${pr.number} | Token: #${usedKey} | Severity: ${severity}_`,
            ].join('\n');

            await github.rest.pulls.createReview({
              owner:       context.repo.owner,
              repo:        context.repo.repo,
              pull_number: pr.number,
              event,
              body,
            });

            console.log(`Posted ${event} review for PR #${pr.number} (token #${usedKey}, severity: ${severity})`);

            if (event === 'REQUEST_CHANGES') {
              core.setFailed('AI review requested changes (CRITICAL issues). Fix the issues before merging.');
            }

      - name: Auto-fix via Copilot (WARNING only)
        if: steps.review.outputs.verdict == 'REQUEST_CHANGES'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const review = fs.readFileSync('/tmp/review_output.txt', 'utf8');
            const hasCritical = /CRITICAL|security|injection|auth.*bypass/i.test(review);
            const pr = context.payload.pull_request;
            
            if (hasCritical) {
              // CRITICAL: needs-human ラベル付与して停止
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: ['needs-human']
              });
              console.log('CRITICAL found. Added needs-human label.');
              return;
            }
            
            // WARNING のみ: リトライ回数チェック
            const comments = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: pr.number,
              per_page: 100
            });
            const autoFixCount = comments.data.filter(c => c.body.includes('[Auto-fix attempt')).length;
            
            if (autoFixCount >= 2) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: ['needs-human']
              });
              console.log('Max retries reached. Added needs-human label.');
              return;
            }
            
            // Required Changes セクションを抽出
            const changesMatch = review.match(/## Required Changes\n([\s\S]*?)(\n---|\n##|$)/);
            const changes = changesMatch ? changesMatch[1].trim() : 'See review comments above.';
            
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: pr.number,
              body: `@copilot [Auto-fix attempt ${autoFixCount + 1}/2] Please fix the following issues:\n\n${changes}`
            });

      - name: Notify review failure
        if: steps.review.outcome == 'failure'
        uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo:  context.repo.repo,
              issue_number: pr.number,
              body: '⚠️ AI Code Review failed (all 3 OAuth tokens exhausted or rate-limited). Manual review required.',
            });
