name: Toolchain Multi-OS Integration Test

on:
  workflow_dispatch:
    inputs:
      languages:
        description: 'テストする言語 (スペース区切り, 例: cpp python rust)'
        required: true
        default: 'cpp c python rust typescript javascript'

jobs:
  integration-test:
    name: Test on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20.x
          cache: npm

      - name: Install Dependencies
        run: npm ci

      - name: Install GNU time on Linux
        if: matrix.os == 'ubuntu-latest'
        run: |
          sudo apt-get update
          sudo apt-get install -y time

      - name: Build TypeScript CLI
        run: npm run build

      - name: Pre-setup Windows environment
        if: matrix.os == 'windows-latest'
        shell: pwsh
        run: |
          winget source update

      - name: Create Dummy Config File
        shell: bash
        run: |
          mkdir -p .atcoder-next
          cat << 'EOF' > .atcoder-next/settings.json
          {
            "defaultLanguage": "cpp",
            "testDirName": "tests",
            "lang": "en",
            "languages": {
              "cpp": {
                "extension": "cpp",
                "templateDir": "templates/cpp",
                "build": "g++ -O2 -std=gnu++20 -o a.out main.cpp",
                "run": "./a.out",
                "submitFile": "main.cpp",
                "atcoderLanguage": "",
                "atcoderLanguageIdRegex": ""
              },
              "python": {
                "extension": "py",
                "templateDir": "templates/python",
                "build": "",
                "run": "python3 main.py",
                "submitFile": "main.py",
                "atcoderLanguage": "",
                "atcoderLanguageIdRegex": ""
              },
              "rust": {
                "extension": "rs",
                "templateDir": "templates/rust",
                "build": "rustc -O -o a.out main.rs",
                "run": "./a.out",
                "submitFile": "main.rs",
                "atcoderLanguage": "",
                "atcoderLanguageIdRegex": ""
              },
              "typescript": {
                "extension": "ts",
                "templateDir": "templates/typescript",
                "build": "",
                "run": "npx --yes ts-node main.ts",
                "submitFile": "main.ts",
                "atcoderLanguage": "",
                "atcoderLanguageIdRegex": ""
              },
              "javascript": {
                "extension": "js",
                "templateDir": "templates/javascript",
                "build": "",
                "run": "node main.js",
                "submitFile": "main.js",
                "atcoderLanguage": "",
                "atcoderLanguageIdRegex": ""
              },
              "c": {
                "extension": "c",
                "templateDir": "templates/c",
                "build": "gcc -O2 -std=c11 -o a.out main.c",
                "run": "./a.out",
                "submitFile": "main.c",
                "atcoderLanguage": "",
                "atcoderLanguageIdRegex": ""
              }
            }
          }
          EOF

      - name: Create Mock Compiler Cache
        shell: bash
        run: |
          mkdir -p ~/.atcoder-next/cache
          cat << 'EOF' > ~/.atcoder-next/cache/compilers.json
          {
            "timestamp": 9999999999999,
            "compilers": [
              { "id": "5001", "name": "C++ 23 (GCC 15.2.0)" },
              { "id": "5002", "name": "C++ 23 (Clang 21.1.0)" },
              { "id": "5003", "name": "Python (3.13.7)" },
              { "id": "5004", "name": "Python (PyPy 3.10-v7.3.20)" },
              { "id": "5005", "name": "Rust (rustc 1.89.0)" },
              { "id": "5006", "name": "TypeScript 5.9 (Node.js 22.19.0)" },
              { "id": "5007", "name": "JavaScript (Node.js 22.19.0)" },
              { "id": "5008", "name": "C11 (GCC 14.2.0)" },
              { "id": "5009", "name": "C11 (Clang 21.1.0)" }
            ]
          }
          EOF

      - name: Run Toolchain Installation Test
        shell: bash
        run: |
          # node経由でビルド済みスクリプトを実行し、--yes で非対話型インストールを行う
          # ログを setup.log に保存しつつコンソールにも出力
          node dist/cli.js tools setup ${{ github.event.inputs.languages }} --yes 2>&1 | tee setup.log

      - name: Add Installed Tools to PATH
        shell: bash
        run: |
          # If Node 22.19.0 was installed via nvm (UNIX), add its bin directory to GITHUB_PATH
          if [ -d "$HOME/.nvm/versions/node/v22.19.0/bin" ]; then
            echo "$HOME/.nvm/versions/node/v22.19.0/bin" >> $GITHUB_PATH
            echo "Added $HOME/.nvm/versions/node/v22.19.0/bin to GITHUB_PATH"
          fi
          # If on Windows, add MSYS2 and NodeJS to PATH
          if [ "$RUNNER_OS" == "Windows" ]; then
            echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
            echo "C:\Program Files\nodejs" >> $GITHUB_PATH
            echo "Added MSYS2 and NodeJS to GITHUB_PATH on Windows"
          fi

      - name: Run Toolchain Doctor Test
        shell: bash
        run: |
          # doctorを非対話モード (--yes) で実行し、バージョンが正しく一致 (Match/Warning) しているか検証
          # インストールした言語のみを検証対象にする
          node dist/cli.js tools doctor ${{ github.event.inputs.languages }} --yes 2>&1 | tee doctor.log || true

      - name: Run Scaffolding, Compile, and Test Integration
        shell: bash
        run: |
          echo "=== Scaffolding, Compile and Test Integration ==="
          for lang in ${{ github.event.inputs.languages }}; do
            echo "Testing scaffolding and local compiler settings for: $lang"
            
            # settings.json の defaultLanguage を一時的に $lang に書き換え
            # typescriptの場合は起動オーバーヘッドを避けるためbuildとrunを上書き
            node -e "
              const fs = require('fs');
              const conf = JSON.parse(fs.readFileSync('.atcoder-next/settings.json', 'utf8'));
              conf.defaultLanguage = '$lang';
              if ('$lang' === 'typescript') {
                conf.languages.typescript.build = 'npx tsc main.ts --target es2022 --module commonjs --moduleResolution node';
                conf.languages.typescript.run = 'node main.js';
              }
              fs.writeFileSync('.atcoder-next/settings.json', JSON.stringify(conf, null, 2));
            "
            
            # 既存のコンテストディレクトリがあればクリーンアップ
            rm -rf abs
            
            # 1. 新しい問題 (abs 1) を作成
            node dist/cli.js new abs 1
            
            # 2. $lang 用の正解 (AC) コードを生成
            case "$lang" in
              cpp)
                echo '#include <iostream>' > abs/1/main.cpp
                echo '#include <string>' >> abs/1/main.cpp
                echo 'using namespace std;' >> abs/1/main.cpp
                echo 'int main() {' >> abs/1/main.cpp
                echo '    int a, b, c;' >> abs/1/main.cpp
                echo '    string s;' >> abs/1/main.cpp
                echo '    if (cin >> a >> b >> c >> s) {' >> abs/1/main.cpp
                echo '        cout << (a + b + c) << " " << s << endl;' >> abs/1/main.cpp
                echo '    }' >> abs/1/main.cpp
                echo '    return 0;' >> abs/1/main.cpp
                echo '}' >> abs/1/main.cpp
                ;;
              c)
                echo '#include <stdio.h>' > abs/1/main.c
                echo 'int main() {' >> abs/1/main.c
                echo '    int a, b, c;' >> abs/1/main.c
                echo '    char s[101];' >> abs/1/main.c
                echo '    if (scanf("%d %d %d %s", &a, &b, &c, s) == 4) {' >> abs/1/main.c
                echo '        printf("%d %s\n", a + b + c, s);' >> abs/1/main.c
                echo '    }' >> abs/1/main.c
                echo '    return 0;' >> abs/1/main.c
                echo '}' >> abs/1/main.c
                ;;
              python)
                echo 'import sys' > abs/1/main.py
                echo 'def main():' >> abs/1/main.py
                echo '    lines = sys.stdin.read().split()' >> abs/1/main.py
                echo '    if len(lines) >= 4:' >> abs/1/main.py
                echo '        a = int(lines[0])' >> abs/1/main.py
                echo '        b = int(lines[1])' >> abs/1/main.py
                echo '        c = int(lines[2])' >> abs/1/main.py
                echo '        s = lines[3]' >> abs/1/main.py
                echo '        print(f"{a+b+c} {s}")' >> abs/1/main.py
                echo "if __name__ == '__main__':" >> abs/1/main.py
                echo '    main()' >> abs/1/main.py
                ;;
              rust)
                echo 'use std::io::{self, Read};' > abs/1/main.rs
                echo 'fn main() {' >> abs/1/main.rs
                echo '    let mut buffer = String::new();' >> abs/1/main.rs
                echo '    io::stdin().read_to_string(&mut buffer).unwrap();' >> abs/1/main.rs
                echo '    let mut words = buffer.split_whitespace();' >> abs/1/main.rs
                echo '    let a: i32 = words.next().unwrap().parse().unwrap();' >> abs/1/main.rs
                echo '    let b: i32 = words.next().unwrap().parse().unwrap();' >> abs/1/main.rs
                echo '    let c: i32 = words.next().unwrap().parse().unwrap();' >> abs/1/main.rs
                echo '    let s = words.next().unwrap();' >> abs/1/main.rs
                echo '    println!("{} {}", a + b + c, s);' >> abs/1/main.rs
                echo '}' >> abs/1/main.rs
                ;;
              typescript)
                echo "import * as fs from 'fs';" > abs/1/main.ts
                echo 'function main() {' >> abs/1/main.ts
                echo "    const input = fs.readFileSync(0, 'utf8');" >> abs/1/main.ts
                echo '    const words = input.trim().split(/\s+/);' >> abs/1/main.ts
                echo '    if (words.length >= 4) {' >> abs/1/main.ts
                echo '        const a = parseInt(words[0], 10);' >> abs/1/main.ts
                echo '        const b = parseInt(words[1], 10);' >> abs/1/main.ts
                echo '        const c = parseInt(words[2], 10);' >> abs/1/main.ts
                echo '        const s = words[3];' >> abs/1/main.ts
                echo '        console.log(`${a + b + c} ${s}`);' >> abs/1/main.ts
                echo '    }' >> abs/1/main.ts
                echo '}' >> abs/1/main.ts
                echo 'main();' >> abs/1/main.ts
                ;;
              javascript)
                echo "const fs = require('fs');" > abs/1/main.js
                echo 'function main() {' >> abs/1/main.js
                echo '    const input = fs.readFileSync(0, "utf8");' >> abs/1/main.js
                echo '    const words = input.trim().split(/\s+/);' >> abs/1/main.js
                echo '    if (words.length >= 4) {' >> abs/1/main.js
                echo '        const a = parseInt(words[0], 10);' >> abs/1/main.js
                echo '        const b = parseInt(words[1], 10);' >> abs/1/main.js
                echo '        const c = parseInt(words[2], 10);' >> abs/1/main.js
                echo '        const s = words[3];' >> abs/1/main.js
                echo '        console.log(`${a + b + c} ${s}`);' >> abs/1/main.js
                echo '    }' >> abs/1/main.js
                echo '}' >> abs/1/main.js
                echo 'main();' >> abs/1/main.js
                ;;
            esac
            
            # 3. テストを実行し、すべてのテストケースがパスする (AC) か確かめる
            # test コマンドは、一部でも WA やエラーがあると非ゼロの終了コードを返すため、
            # 失敗時にはここでワークフロー全体がエラー終了します。
            node dist/cli.js test abs 1 2>&1 | tee test.log
            
            # メモリ使用量が正常に計測・出力されているか確認
            echo "Checking if memory usage is displayed in test output..."
            grep -q "Memory:" test.log || (echo "Error: Memory usage was not measured or displayed in test output for $lang!" && exit 1)
            
            echo "Successfully verified compile & test with memory measurement for $lang!"
          done

      - name: Verify Installed Tools (Bash)
        shell: bash
        run: |
          echo "=== Verification ==="
          for lang in ${{ github.event.inputs.languages }}; do
            echo "Verifying $lang..."
            if [ "$lang" = "cpp" ] || [ "$lang" = "c" ]; then
              g++ --version || clang++ --version || cl.exe
            elif [ "$lang" = "python" ]; then
              python3 --version || python --version || pypy3 --version
            elif [ "$lang" = "rust" ]; then
              rustc --version
            fi
          done

      - name: Copy Install Log to Workspace
        if: always()
        shell: bash
        run: |
          # 各OS共通のホームディレクトリパスから install.log をワークスペースにコピー
          if [ -f ~/.atcoder-next/install.log ]; then
            cp ~/.atcoder-next/install.log ./install.log
            echo "install.log copied successfully"
          else
            echo "No install.log found at ~/.atcoder-next/install.log"
          fi

      - name: Upload Logs
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: toolchain-logs-${{ matrix.os }}
          path: |
            setup.log
            doctor.log
            install.log
          if-no-files-found: ignore
