name: Test Coverage

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  test-coverage:
    name: Test and Upload Coverage
    runs-on: ubuntu-22.04

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

      - name: Setup Bun
        uses: oven-sh/setup-bun@v1
        with:
          bun-version: latest

      - name: Install dependencies
        run: |
          # Configure timeout for onnxruntime download
          export ONNXRUNTIME_DOWNLOAD_TIMEOUT=600000
          # Try Bun install with fallback to npm
          bun install --frozen-lockfile || {
            echo "Bun install failed, trying npm..."
            npm config set fetch-timeout 300000
            npm config set fetch-retries 5
            npm ci --no-audit --no-fund --omit=optional || \
            npm install --no-audit --no-fund --omit=optional
          }

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Install Linux dependencies for Tauri bindings
        run: |
          sudo apt-get update
          sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libglib2.0-dev
          # Install FFmpeg dependencies needed for TypeScript bindings generation
          sudo apt-get install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libavdevice-dev libswscale-dev libswresample-dev pkg-config
          # Set PKG_CONFIG_PATH
          echo "PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig" >> $GITHUB_ENV

      - name: Create dist directory
        run: mkdir -p dist

      - name: Build Rust dependencies
        env:
          PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
        run: |
          cd src-tauri
          cargo build --release

      - name: Generate TypeScript bindings
        env:
          PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
        run: |
          cd src-tauri
          cargo run --bin generate_types
          cd ..
          # Verify TypeScript bindings were generated
          if [ ! -f "src/types/generated/tauri-bindings.ts" ]; then
            echo "Error: TypeScript bindings were not generated!"
            echo "Contents of src/types/generated/:"
            ls -la src/types/generated/ || echo "Directory does not exist"
            exit 1
          fi
          echo "TypeScript bindings generated successfully"

      - name: Create test results directory
        run: mkdir -p test-results

      - name: Run tests with coverage
        run: bun run test:coverage
        env:
          CI: true
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
          CODECOV_STATIC_TOKEN: ${{ secrets.CODECOV_STATIC_TOKEN }}

      - name: Upload test results to Codecov
        if: ${{ !cancelled() }}
        uses: codecov/test-results-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          file: ./test-results/junit.xml
          flags: frontend
          name: frontend-tests
          fail_ci_if_error: false
          verbose: true

      - name: Upload coverage to Codecov (with plugin)
        if: always()
        run: bun run test:coverage
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
          CODECOV_STATIC_TOKEN: ${{ secrets.CODECOV_STATIC_TOKEN }}
          CI: true

      - name: Upload coverage reports to Codecov (fallback)
        if: always()
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage/lcov.info
          flags: frontend
          name: frontend-coverage
          fail_ci_if_error: false
          verbose: true
          slug: chatman-media/timeline-studio

  test-rust-coverage:
    name: Rust Test Coverage
    runs-on: ubuntu-22.04

    steps:
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          # this might remove tools that are actually needed,
          # if set to "true" but frees about 6 GB
          tool-cache: false
          # all of these default to true, but feel free to set to
          # "false" if necessary for your workflow
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v1
        with:
          bun-version: latest

      - name: Install dependencies
        run: |
          # Configure timeout for onnxruntime download
          export ONNXRUNTIME_DOWNLOAD_TIMEOUT=600000
          # Try Bun install with fallback to npm
          bun install --frozen-lockfile || {
            echo "Bun install failed, trying npm..."
            npm config set fetch-timeout 300000
            npm config set fetch-retries 5
            npm ci --no-audit --no-fund --omit=optional || \
            npm install --no-audit --no-fund --omit=optional
          }

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools-preview

      - name: Cache cargo registry
        uses: actions/cache@v4
        with:
          path: ~/.cargo/registry
          key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-registry-

      - name: Cache cargo index
        uses: actions/cache@v4
        with:
          path: ~/.cargo/git
          key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-index-

      - name: Cache cargo build
        uses: actions/cache@v4
        with:
          path: |
            src-tauri/target/debug/deps
            src-tauri/target/debug/build
          key: ${{ runner.os }}-cargo-build-deps-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-build-deps-

      - name: Cache cargo-llvm-cov
        uses: actions/cache@v4
        with:
          path: ~/.cargo/bin/cargo-llvm-cov
          key: ${{ runner.os }}-cargo-llvm-cov-0.5.39

      - name: Install cargo-llvm-cov
        run: |
          if ! command -v cargo-llvm-cov &> /dev/null; then
            echo "Installing cargo-llvm-cov..."
            # Try to download pre-built binary first
            curl -LsSf https://github.com/taiki-e/cargo-llvm-cov/releases/download/v0.5.39/cargo-llvm-cov-x86_64-unknown-linux-gnu.tar.gz | tar xzf - -C ~/.cargo/bin || {
              echo "Failed to download binary, installing from source..."
              cargo install cargo-llvm-cov --version 0.5.39 --locked
            }
          else
            echo "cargo-llvm-cov already installed: $(cargo-llvm-cov --version)"
          fi

      - name: Install cargo2junit
        run: |
          if ! command -v cargo2junit &> /dev/null; then
            cargo install cargo2junit --locked
          fi

      - name: Install Linux dependencies
        run: |
          sudo apt-get update
          # Install essential build tools and C/C++ headers first
          sudo apt-get install -y build-essential gcc g++ make cmake
          # Install Clang and LLVM for bindgen
          sudo apt-get install -y clang libclang-dev llvm-dev
          # Try to install specific clang version if available
          sudo apt-get install -y libclang-14-dev || sudo apt-get install -y libclang-13-dev || sudo apt-get install -y libclang-12-dev || true
          # Install standard library headers
          sudo apt-get install -y libc6-dev linux-libc-dev
          # Install other required dependencies
          sudo apt-get install -y libwebkit2gtk-4.1-dev curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
          # Install FFmpeg and development libraries
          sudo apt-get install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libavdevice-dev libswscale-dev libswresample-dev pkg-config
      
      - name: Setup FFmpeg environment and diagnostics
        run: |
          echo "=== FFmpeg Diagnostics ==="
          echo "Installed packages:"
          dpkg -l | grep -E "(ffmpeg|libav|libsw)" | head -10
          
          echo ""
          echo "Searching for FFmpeg headers system-wide..."
          find /usr -name "swscale.h" 2>/dev/null | head -5 || echo "No swscale.h found"
          find /usr -name "avcodec.h" 2>/dev/null | head -5 || echo "No avcodec.h found"
          
          echo ""
          echo "Checking common include directories..."
          ls -la /usr/include/ | grep -E "(libav|libsw|ffmpeg)" || echo "No FFmpeg directories in /usr/include"
          ls -la /usr/include/x86_64-linux-gnu/ | grep -E "(libav|libsw|ffmpeg)" || echo "No FFmpeg directories in /usr/include/x86_64-linux-gnu"
          
          echo ""
          echo "pkg-config status:"
          export PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"
          pkg-config --exists libswscale && echo "✅ pkg-config finds libswscale" || echo "❌ pkg-config cannot find libswscale"
          pkg-config --cflags libswscale 2>/dev/null || echo "No cflags for libswscale"
          pkg-config --libs libswscale 2>/dev/null || echo "No libs for libswscale"
          
          echo ""
          echo "=== Attempting to fix header locations ==="
          
          # Check if headers are in expected location
          if [ ! -f "/usr/include/libswscale/swscale.h" ]; then
            echo "swscale.h not found in /usr/include, searching for actual location..."
            
            # More comprehensive search
            SWSCALE_PATHS=$(find /usr -name "swscale.h" 2>/dev/null)
            echo "Found swscale.h files at:"
            echo "$SWSCALE_PATHS"
            
            # Try to find the main one in libswscale directory
            MAIN_SWSCALE=$(echo "$SWSCALE_PATHS" | grep "libswscale/swscale.h" | head -1)
            
            if [ -n "$MAIN_SWSCALE" ]; then
              # Extract base directory (parent of libswscale)
              BASE_DIR=$(dirname $(dirname "$MAIN_SWSCALE"))
              echo "Found main FFmpeg headers in: $BASE_DIR"
              
              # Create symlinks to standard location
              echo "Creating symlinks to /usr/include..."
              FFMPEG_LIBS=("libavcodec" "libavformat" "libavutil" "libswscale" "libavfilter" "libavdevice" "libswresample")
              for lib in "${FFMPEG_LIBS[@]}"; do
                if [ -d "$BASE_DIR/$lib" ] && [ ! -L "/usr/include/$lib" ]; then
                  sudo ln -sfn "$BASE_DIR/$lib" "/usr/include/$lib"
                  echo "✅ Created symlink: /usr/include/$lib -> $BASE_DIR/$lib"
                elif [ -L "/usr/include/$lib" ]; then
                  echo "✅ Symlink already exists: /usr/include/$lib"
                else
                  echo "⚠️ Directory not found: $BASE_DIR/$lib"
                fi
              done
              
              # Verify the symlinks worked
              echo ""
              echo "Verification after symlink creation:"
              if [ -f "/usr/include/libswscale/swscale.h" ]; then
                echo "✅ swscale.h now accessible at /usr/include/libswscale/swscale.h"
              else
                echo "❌ swscale.h still not found at /usr/include/libswscale/swscale.h"
              fi
            else
              echo "❌ ERROR: No libswscale/swscale.h found in any location!"
              echo "All swscale.h locations:"
              echo "$SWSCALE_PATHS"
              exit 1
            fi
          else
            echo "✅ swscale.h already found at /usr/include/libswscale/swscale.h"
          fi
          
          # Set environment variables for pkg-config
          echo "PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig" >> $GITHUB_ENV
          echo "PKG_CONFIG_ALLOW_SYSTEM_LIBS=1" >> $GITHUB_ENV
          echo "PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1" >> $GITHUB_ENV
          
      - name: Ensure clean FFmpeg environment
        run: |
          # Clear any FFmpeg environment variables that might be set from .env files
          unset FFMPEG_DIR
          unset FFMPEG_INCLUDE_DIR
          unset FFMPEG_LIB_DIR
          unset BINDGEN_EXTRA_CLANG_ARGS
          unset DYLD_FALLBACK_LIBRARY_PATH
          
          # Export clean environment
          echo "FFMPEG_DIR=" >> $GITHUB_ENV
          echo "FFMPEG_INCLUDE_DIR=" >> $GITHUB_ENV
          echo "FFMPEG_LIB_DIR=" >> $GITHUB_ENV
          
          # Find and set up libclang for bindgen
          echo "=== Finding libclang ==="
          
          # Variable to track if we found libclang
          FOUND_LIBCLANG=""
          
          # First try to find versioned libclang
          for version in 14 13 12 11; do
            if [ -f "/usr/lib/llvm-$version/lib/libclang.so" ]; then
              echo "Found libclang version $version"
              echo "LIBCLANG_PATH=/usr/lib/llvm-$version/lib" >> $GITHUB_ENV
              echo "✅ Set LIBCLANG_PATH=/usr/lib/llvm-$version/lib"
              FOUND_LIBCLANG="yes"
              break
            elif [ -f "/usr/lib/x86_64-linux-gnu/libclang-$version.so" ]; then
              echo "Found libclang version $version in x86_64-linux-gnu"
              echo "LIBCLANG_PATH=/usr/lib/x86_64-linux-gnu" >> $GITHUB_ENV
              echo "✅ Set LIBCLANG_PATH=/usr/lib/x86_64-linux-gnu"
              FOUND_LIBCLANG="yes"
              break
            fi
          done
          
          # If not found, search more broadly
          if [ -z "$FOUND_LIBCLANG" ]; then
            LIBCLANG_SO=$(find /usr -name "libclang.so*" -type f 2>/dev/null | grep -E "libclang(-[0-9]+)?\.so" | head -1)
            if [ -n "$LIBCLANG_SO" ]; then
              LIBCLANG_DIR=$(dirname "$LIBCLANG_SO")
              echo "Found libclang at: $LIBCLANG_SO"
              echo "LIBCLANG_PATH=$LIBCLANG_DIR" >> $GITHUB_ENV
              echo "✅ Set LIBCLANG_PATH=$LIBCLANG_DIR"
              FOUND_LIBCLANG="yes"
            else
              echo "❌ ERROR: Could not find libclang.so!"
              echo "Installed clang packages:"
              dpkg -l | grep clang || echo "No clang packages found"
              echo ""
              echo "Searching for any libclang files:"
              find /usr -name "*libclang*" -type f 2>/dev/null | head -20
              exit 1
            fi
          fi
          
          # Set up bindgen clang args
          echo "BINDGEN_EXTRA_CLANG_ARGS=-I/usr/include -I/usr/include/x86_64-linux-gnu" >> $GITHUB_ENV
          
      - name: Install ONNX Runtime
        run: |
          # Download ONNX Runtime 1.22.0 for Linux x64 (compatible with ort 2.0.0-rc.10)
          wget https://github.com/microsoft/onnxruntime/releases/download/v1.22.0/onnxruntime-linux-x64-1.22.0.tgz
          tar -xzf onnxruntime-linux-x64-1.22.0.tgz
          # Copy only library files to system path
          sudo cp onnxruntime-linux-x64-1.22.0/lib/*.so* /usr/local/lib/
          # Update library cache
          sudo ldconfig
          # Set environment variable for dynamic loading
          echo "ORT_DYLIB_PATH=/usr/local/lib/libonnxruntime.so" >> $GITHUB_ENV

      - name: Create empty dist directory
        run: mkdir dist

      - name: Check system resources before tests
        run: |
          echo "=== Disk Space ==="
          df -h
          echo ""
          echo "=== Memory Info ==="
          free -h
          echo ""
          echo "=== CPU Info ==="
          nproc
          lscpu | grep "Model name"

      - name: Create test results directory
        run: mkdir -p test-results

      - name: Clean target directory before coverage
        run: |
          cd src-tauri
          # Remove non-essential build artifacts to save space
          cargo clean --release
          # Also clean doc and example builds
          rm -rf target/doc
          rm -rf target/debug/examples
          rm -rf target/debug/incremental
          cd ..

      - name: Debug FFmpeg environment before tests
        run: |
          echo "=== FFmpeg Environment Debug ==="
          echo "FFMPEG_DIR: $FFMPEG_DIR"
          echo "FFMPEG_INCLUDE_DIR: $FFMPEG_INCLUDE_DIR"
          echo "FFMPEG_LIB_DIR: $FFMPEG_LIB_DIR"
          echo "PKG_CONFIG_PATH: $PKG_CONFIG_PATH"
          echo ""
          echo "=== Clang/Bindgen Environment ==="
          echo "LIBCLANG_PATH: $LIBCLANG_PATH"
          echo "BINDGEN_EXTRA_CLANG_ARGS: $BINDGEN_EXTRA_CLANG_ARGS"
          echo ""
          echo "=== All environment variables with FFMPEG ==="
          env | grep -i ffmpeg || echo "No FFMPEG variables found"
          echo ""
          echo "=== pkg-config check ==="
          pkg-config --exists libavcodec && echo "✅ libavcodec found" || echo "❌ libavcodec not found"
          pkg-config --cflags libavcodec || echo "No cflags"
          pkg-config --libs libavcodec || echo "No libs"
          
      - name: Run Rust tests with coverage
        run: |
          cd src-tauri
          # Set up environment for reduced memory usage
          export CARGO_BUILD_JOBS=2
          export CARGO_INCREMENTAL=0
          
          # Generate coverage report using cargo-llvm-cov (this also runs tests)
          if command -v cargo-llvm-cov &> /dev/null; then
            echo "Generating coverage report with cargo-llvm-cov..."
            # Run tests and generate coverage in one pass
            # Test only the library target to focus on actual code coverage
            # Use --no-fail-fast to continue on test failures
            cargo llvm-cov --lcov --output-path coverage.info \
              --lib \
              --bins \
              --tests \
              --no-fail-fast \
              -- --test-threads=2
          else
            echo "cargo-llvm-cov not found, running tests without coverage"
            cargo test --lib --bins --tests -- --test-threads=2
          fi
          
          # Generate JUnit report separately if needed
          if command -v cargo2junit &> /dev/null && [ ! -f "../test-results/rust-junit.xml" ]; then
            cargo test --lib --no-fail-fast --message-format json -- --test-threads=2 2>/dev/null | cargo2junit > ../test-results/rust-junit.xml || echo "cargo2junit failed"
          fi
          cd ..
        env:
          RUSTFLAGS: -Cinstrument-coverage -Copt-level=0 -Ccodegen-units=1
          LLVM_PROFILE_FILE: timeline-studio-%p-%m.profraw
          # Ensure FFmpeg uses system packages via pkg-config
          PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
          PKG_CONFIG_ALLOW_SYSTEM_LIBS: 1
          PKG_CONFIG_ALLOW_SYSTEM_CFLAGS: 1
          # Reduce parallel compilation to save memory
          CARGO_BUILD_JOBS: 2

      - name: Check system resources after tests
        if: always()
        run: |
          echo "=== Disk Space ==="
          df -h
          echo ""
          echo "=== Memory Info ==="
          free -h

      - name: Debug coverage files
        if: always()
        run: |
          echo "=== Coverage files debug ==="
          echo "Current directory: $(pwd)"
          echo "Files in src-tauri:"
          ls -la src-tauri/coverage.* 2>/dev/null || echo "No coverage files in src-tauri"
          echo "Files in test-results:"
          ls -la test-results/ 2>/dev/null || echo "No files in test-results"
          echo "Checking if coverage.info exists:"
          if [ -f "src-tauri/coverage.info" ]; then
            echo "✅ coverage.info found, size: $(stat -c%s src-tauri/coverage.info 2>/dev/null || stat -f%z src-tauri/coverage.info) bytes"
            echo "First 10 lines:"
            head -10 src-tauri/coverage.info
          else
            echo "❌ coverage.info not found"
          fi

      - name: Upload Rust test results to Codecov
        if: ${{ !cancelled() && hashFiles('./test-results/rust-junit.xml') != '' }}
        uses: codecov/test-results-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          file: ./test-results/rust-junit.xml
          flags: rust
          name: rust-tests
          fail_ci_if_error: false
          verbose: true

      - name: Upload Rust coverage to Codecov
        if: ${{ always() && hashFiles('./src-tauri/coverage.info') != '' }}
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./src-tauri/coverage.info
          flags: rust
          name: rust-coverage
          fail_ci_if_error: false
          verbose: true
          slug: chatman-media/timeline-studio