name: Release (kimi-cli)

on:
  push:
    tags:
      - "[0-9]*"

permissions:
  contents: write

jobs:
  validate:
    name: Validate tag
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python 3.14
        uses: actions/setup-python@v5
        with:
          python-version: "3.14"
          allow-prereleases: true

      - name: Check version tag
        run: |
          python scripts/check_version_tag.py \
            --pyproject pyproject.toml \
            --expected-version "${GITHUB_REF_NAME}"

      - name: Check kimi-code version tag
        run: |
          python scripts/check_version_tag.py \
            --pyproject packages/kimi-code/pyproject.toml \
            --expected-version "${GITHUB_REF_NAME}"

      - name: Check dependency versions
        run: |
          python scripts/check_kimi_dependency_versions.py \
            --root-pyproject pyproject.toml \
            --kosong-pyproject packages/kosong/pyproject.toml \
            --pykaos-pyproject packages/kaos/pyproject.toml

  build:
    name: Build binaries (${{ matrix.target }})
    needs: validate
    strategy:
      fail-fast: false
      matrix:
        include:
          - runner: ubuntu-22.04
            target: x86_64-unknown-linux-gnu
          - runner: ubuntu-22.04-arm
            target: aarch64-unknown-linux-gnu
          - runner: macos-14
            target: aarch64-apple-darwin
          - runner: windows-2022
            target: x86_64-pc-windows-msvc
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install GNU Make (Windows)
        if: runner.os == 'Windows'
        run: choco install make -y

      - name: Set up Python 3.14
        uses: actions/setup-python@v5
        with:
          python-version: "3.14"
          allow-prereleases: true

      - name: Set up uv
        uses: astral-sh/setup-uv@v1
        with:
          version: "0.8.5"

      - name: Prepare building environment
        run: make prepare-build

      # macOS: Setup signing certificate before build
      - name: Setup macOS signing certificate
        if: runner.os == 'macOS'
        env:
          APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          KEYCHAIN_PASSWORD: actions
        run: |
          set -euo pipefail

          # Decode certificate
          cert_path="${RUNNER_TEMP}/certificate.p12"
          echo "$APPLE_CERTIFICATE_P12" | base64 -d > "$cert_path"

          # Create temporary keychain
          keychain_path="${RUNNER_TEMP}/signing.keychain-db"
          security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
          security set-keychain-settings -lut 21600 "$keychain_path"
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"

          # Add to keychain search list
          security list-keychains -d user -s "$keychain_path" $(security list-keychains -d user | tr -d '"')
          security default-keychain -s "$keychain_path"

          # Import certificate
          security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
          security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null

          # Find signing identity
          IDENTITY=$(security find-identity -v -p codesigning "$keychain_path" | grep "Developer ID Application" | head -1 | sed -n 's/.*"\(Developer ID Application[^"]*\)".*/\1/p')
          
          if [[ -z "$IDENTITY" ]]; then
            echo "❌ No Developer ID Application identity found"
            security find-identity -v -p codesigning "$keychain_path"
            exit 1
          fi

          echo "✅ Found signing identity: $IDENTITY"
          echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV"
          echo "APPLE_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV"

          rm -f "$cert_path"

      # Build onefile and onedir versions (all platforms)
      - name: Build standalone binary (onefile)
        run: make build-bin

      - name: Build standalone binary (onedir)
        env:
          PYINSTALLER_ONEDIR: "1"
        run: make build-bin-onedir

      # macOS: Sign onefile binary
      - name: Sign macOS onefile binary
        if: runner.os == 'macOS'
        run: |
          set -euo pipefail
          echo "Signing onefile binary..."
          codesign --deep --force --options runtime --timestamp \
            --sign "$APPLE_SIGNING_IDENTITY" \
            --keychain "$APPLE_KEYCHAIN_PATH" \
            dist/onefile/kimi
          echo "✅ Onefile binary signed"
          codesign -dv --verbose=2 dist/onefile/kimi

      # macOS: Sign onedir binaries (all dylibs and executables)
      - name: Sign macOS onedir binaries
        if: runner.os == 'macOS'
        run: |
          set -euo pipefail
          echo "Signing onedir binaries..."
          
          # 1. Sign all dylibs and so files first (excluding those inside frameworks)
          find dist/onedir/kimi -type f \( -name "*.dylib" -o -name "*.so" \) ! -path "*.framework/*" | while read -r lib; do
            echo "Signing: $lib"
            codesign --force --options runtime --timestamp \
              --sign "$APPLE_SIGNING_IDENTITY" \
              --keychain "$APPLE_KEYCHAIN_PATH" \
              "$lib"
          done
          
          # 2. Sign all frameworks with --deep (important for Python.framework)
          find dist/onedir/kimi -type d -name "*.framework" | while read -r framework; do
            echo "Signing framework: $framework"
            codesign --deep --force --options runtime --timestamp \
              --sign "$APPLE_SIGNING_IDENTITY" \
              --keychain "$APPLE_KEYCHAIN_PATH" \
              "$framework"
          done
          
          # 3. Sign the main executable last
          echo "Signing main executable: dist/onedir/kimi/kimi"
          codesign --force --options runtime --timestamp \
            --sign "$APPLE_SIGNING_IDENTITY" \
            --keychain "$APPLE_KEYCHAIN_PATH" \
            dist/onedir/kimi/kimi
          
          echo "✅ Onedir binaries signed"
          codesign -dv --verbose=2 dist/onedir/kimi/kimi
          codesign --verify --deep --strict dist/onedir/kimi/kimi && echo "✅ Deep verification passed"

      # macOS: Notarize onefile binary
      - name: Notarize macOS onefile binary
        if: runner.os == 'macOS'
        env:
          APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
          APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
          APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
        run: |
          set -euo pipefail

          # Save API key
          key_path="${RUNNER_TEMP}/AuthKey.p8"
          echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$key_path"

          # Create zip for notarization (use --norsrc to avoid ._ AppleDouble files)
          zip_path="${RUNNER_TEMP}/kimi-onefile.zip"
          ditto -c -k --norsrc --keepParent dist/onefile/kimi "$zip_path"

          echo "Submitting onefile for notarization..."
          
          # Submit and capture output for status verification
          xcrun notarytool submit "$zip_path" \
            --key "$key_path" \
            --key-id "$APPLE_NOTARIZATION_KEY_ID" \
            --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
            --wait \
            --timeout 15m \
            2>&1 | tee /tmp/notarize-onefile.log

          # Verify notarization was accepted
          if ! grep -q "status: Accepted" /tmp/notarize-onefile.log; then
            echo "❌ Onefile notarization failed!"
            cat /tmp/notarize-onefile.log
            
            # Get detailed error log from Apple
            submission_id=$(grep "id:" /tmp/notarize-onefile.log | head -1 | awk '{print $2}')
            if [[ -n "$submission_id" ]]; then
              echo "Fetching notarization log for submission: $submission_id"
              xcrun notarytool log "$submission_id" \
                --key "$key_path" \
                --key-id "$APPLE_NOTARIZATION_KEY_ID" \
                --issuer "$APPLE_NOTARIZATION_ISSUER_ID" 2>&1 || true
            fi
            exit 1
          fi

          echo "✅ Onefile notarization completed and accepted"

          # Verify signature and notarization status
          echo "Verifying onefile signature..."
          codesign -dv --verbose=2 dist/onefile/kimi
          
          echo "Verifying onefile notarization (online check)..."
          spctl -a -vvv -t install dist/onefile/kimi

          # Cleanup
          rm -f "$zip_path" /tmp/notarize-onefile.log

      # macOS: Notarize onedir binaries
      - name: Notarize macOS onedir binaries
        if: runner.os == 'macOS'
        env:
          APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
          APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
          APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
        run: |
          set -euo pipefail

          # Save API key (might already exist from previous step)
          key_path="${RUNNER_TEMP}/AuthKey.p8"
          if [[ ! -f "$key_path" ]]; then
            echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$key_path"
          fi

          # Create zip for notarization (use --norsrc to avoid ._ AppleDouble files)
          zip_path="${RUNNER_TEMP}/kimi-onedir.zip"
          ditto -c -k --norsrc --keepParent dist/onedir/kimi "$zip_path"

          echo "Submitting onedir for notarization..."
          
          # Submit and capture output for status verification
          xcrun notarytool submit "$zip_path" \
            --key "$key_path" \
            --key-id "$APPLE_NOTARIZATION_KEY_ID" \
            --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
            --wait \
            --timeout 15m \
            2>&1 | tee /tmp/notarize-onedir.log

          # Verify notarization was accepted
          if ! grep -q "status: Accepted" /tmp/notarize-onedir.log; then
            echo "❌ Onedir notarization failed!"
            cat /tmp/notarize-onedir.log
            
            # Get detailed error log from Apple
            submission_id=$(grep "id:" /tmp/notarize-onedir.log | head -1 | awk '{print $2}')
            if [[ -n "$submission_id" ]]; then
              echo "Fetching notarization log for submission: $submission_id"
              xcrun notarytool log "$submission_id" \
                --key "$key_path" \
                --key-id "$APPLE_NOTARIZATION_KEY_ID" \
                --issuer "$APPLE_NOTARIZATION_ISSUER_ID" 2>&1 || true
            fi
            exit 1
          fi

          echo "✅ Onedir notarization completed and accepted"

          # Verify signature and notarization status
          echo "Verifying onedir signature..."
          codesign -dv --verbose=2 dist/onedir/kimi/kimi
          
          echo "Verifying onedir notarization (online check)..."
          spctl -a -vvv -t install dist/onedir/kimi/kimi

          # Cleanup
          rm -f "$key_path" "$zip_path" /tmp/notarize-onedir.log

      # macOS: Cleanup keychain
      - name: Cleanup macOS keychain
        if: always() && runner.os == 'macOS'
        run: |
          if [[ -n "${APPLE_KEYCHAIN_PATH:-}" && -f "${APPLE_KEYCHAIN_PATH}" ]]; then
            security delete-keychain "$APPLE_KEYCHAIN_PATH" || true
          fi

      # Package onefile artifact (all platforms)
      - name: Package onefile artifact
        shell: python
        env:
          TAG: ${{ github.ref_name }}
          TARGET: ${{ matrix.target }}
        run: |
          import os
          import pathlib
          import tarfile
          import zipfile

          tag = os.environ["TAG"]
          target = os.environ["TARGET"]

          dist_dir = pathlib.Path("dist")
          artifacts_dir = pathlib.Path("artifacts")
          artifacts_dir.mkdir(parents=True, exist_ok=True)

          is_windows = "windows" in target
          is_macos = "apple-darwin" in target
          binary_name = "kimi.exe" if is_windows else "kimi"
          binary_path = dist_dir / "onefile" / binary_name
          if not binary_path.exists():
              raise SystemExit(f"Binary not found at {binary_path}")

          # Determine archive format and name
          # - Windows: .zip
          # - macOS: .tar.gz
          # - Linux: .tar.gz
          if is_windows:
              archive_name = f"kimi-{tag}-{target}.zip"
              archive_path = artifacts_dir / archive_name
              with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive_file:
                  archive_file.write(binary_path, arcname=binary_name)
          else:
              archive_name = f"kimi-{tag}-{target}.tar.gz"
              archive_path = artifacts_dir / archive_name
              with tarfile.open(archive_path, "w:gz") as archive_file:
                  archive_file.add(binary_path, arcname="kimi")

          print(f"Built onefile artifact: {archive_path}")

      # Package onedir artifact (all platforms)
      - name: Package onedir artifact
        shell: python
        env:
          TAG: ${{ github.ref_name }}
          TARGET: ${{ matrix.target }}
        run: |
          import os
          import pathlib
          import tarfile
          import zipfile

          tag = os.environ["TAG"]
          target = os.environ["TARGET"]

          dist_dir = pathlib.Path("dist")
          artifacts_dir = pathlib.Path("artifacts")
          artifacts_dir.mkdir(parents=True, exist_ok=True)

          is_windows = "windows" in target

          # Windows: onedir is dist/onedir/kimi with kimi.exe inside
          # Others: onedir is dist/onedir/kimi with kimi inside
          onedir_path = dist_dir / "onedir" / "kimi"
          if not onedir_path.exists() or not onedir_path.is_dir():
              raise SystemExit(f"Onedir directory not found at {onedir_path}")

          if is_windows:
              archive_name = f"kimi-{tag}-{target}-onedir.zip"
              archive_path = artifacts_dir / archive_name
              with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive_file:
                  # Add the directory contents with kimi/ as the root
                  for item in onedir_path.rglob("*"):
                      if item.is_file():
                          arcname = f"kimi/{item.relative_to(onedir_path)}"
                          archive_file.write(item, arcname=arcname)
          else:
              archive_name = f"kimi-{tag}-{target}-onedir.tar.gz"
              archive_path = artifacts_dir / archive_name
              with tarfile.open(archive_path, "w:gz") as archive_file:
                  # Add the directory contents with kimi/ as the root
                  for item in onedir_path.iterdir():
                      archive_file.add(item, arcname=f"kimi/{item.name}")

          print(f"Built onedir artifact: {archive_path}")

      - name: Set artifact name
        id: artifact
        shell: python
        run: |
          import os
          ref = os.environ["GITHUB_REF_NAME"].replace("/", "-")
          target = "${{ matrix.target }}"
          with open(os.environ["GITHUB_OUTPUT"], "a") as f:
              f.write(f"name=kimi-{ref}-{target}\n")

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.artifact.outputs.name }}
          path: artifacts/*
          if-no-files-found: error
          retention-days: 7

  release:
    name: Publish GitHub Release
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download all build artifacts
        uses: actions/download-artifact@v4
        with:
          path: ./downloads
          merge-multiple: true

      - name: Show downloaded files
        run: ls -laR downloads

      - name: Generate per-file SHA256 sums
        shell: bash
        run: |
          set -euxo pipefail
          cd downloads
          shopt -s nullglob
          for f in *.tar.gz *.zip; do
            sha256sum "$f" > "$f.sha256"
            echo "sha256($(basename "$f"))=$(cut -d' ' -f1 "$f.sha256")"
          done
          ls -la

      - name: Create GitHub Release and upload assets
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ github.ref_name }}
          name: ${{ github.ref_name }}
          generate_release_notes: true
          files: |
            downloads/*.tar.gz
            downloads/*.zip
            downloads/*.tar.gz.sha256
            downloads/*.zip.sha256
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  publish-python:
    name: Publish Python package
    needs: validate
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python 3.14
        uses: actions/setup-python@v5
        with:
          python-version: "3.14"
          allow-prereleases: true

      - name: Set up uv
        uses: astral-sh/setup-uv@v1
        with:
          version: "0.8.5"

      - name: Build distributions
        run: make build-kimi-cli

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}
          packages-dir: dist
