version: 2.1
orbs:
  node: circleci/node@6.1.0
  qlty-orb: qltysh/qlty-orb@0.0

# Shared defaults for setup steps
defaults: &defaults
  working_directory: ~/build
  machine:
    image: ubuntu-2204:2024.05.1
  resource_class: large
  environment:
    RAILS_LOG_TO_STDOUT: false
    COVERAGE: true
    LOG_LEVEL: warn

jobs:
  # Separate job for linting (no parallelism needed)
  lint:
    <<: *defaults
    steps:
      - checkout

      # Install minimal system dependencies for linting
      - run:
          name: Install System Dependencies
          command: |
            sudo apt-get update
            DEBIAN_FRONTEND=noninteractive sudo apt-get install -y \
              libpq-dev \
              build-essential \
              git \
              curl \
              libssl-dev \
              zlib1g-dev \
              libreadline-dev \
              libyaml-dev \
              openjdk-11-jdk \
              jq \
              software-properties-common \
              ca-certificates \
              imagemagick \
              libxml2-dev \
              libxslt1-dev \
              file \
              g++ \
              gcc \
              autoconf \
              gnupg2 \
              patch \
              ruby-dev \
              liblzma-dev \
              libgmp-dev \
              libncurses5-dev \
              libffi-dev \
              libgdbm6 \
              libgdbm-dev \
              libvips

      - run:
          name: Install RVM and Ruby 3.4.4
          command: |
            sudo apt-get install -y gpg
            gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
            \curl -sSL https://get.rvm.io | bash -s stable
            echo 'source ~/.rvm/scripts/rvm' >> $BASH_ENV
            source ~/.rvm/scripts/rvm
            rvm install "3.4.4"
            rvm use 3.4.4 --default
            gem install bundler -v 2.5.16

      - run:
          name: Install Application Dependencies
          command: |
            source ~/.rvm/scripts/rvm
            bundle install

      - node/install:
          node-version: '24.13'
      - node/install-pnpm
      - node/install-packages:
          pkg-manager: pnpm
          override-ci-command: pnpm i

      # Swagger verification
      - run:
          name: Verify swagger API specification
          command: |
            bundle exec rake swagger:build
            if [[ `git status swagger/swagger.json --porcelain` ]]
            then
              echo "ERROR: The swagger.json file is not in sync with the yaml specification. Run 'rake swagger:build' and commit 'swagger/swagger.json'."
              exit 1
            fi
            mkdir -p ~/tmp
            curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.19.0/openapi-generator-cli-7.19.0.jar > ~/tmp/openapi-generator-cli-7.19.0.jar
            java -jar ~/tmp/openapi-generator-cli-7.19.0.jar validate -i swagger/swagger.json

      # Bundle audit
      - run:
          name: Bundle audit
          command: bundle exec bundle audit update && bundle exec bundle audit check -v

      # Rubocop linting
      - run:
          name: Rubocop
          command: bundle exec rubocop --parallel

      # ESLint linting
      - run:
          name: eslint
          command: pnpm run eslint

  # Separate job for frontend tests
  frontend-tests:
    <<: *defaults
    steps:
      - checkout
      - node/install:
          node-version: '24.13'
      - node/install-pnpm
      - node/install-packages:
          pkg-manager: pnpm
          override-ci-command: pnpm i

      - run:
          name: Run frontend tests (with coverage)
          command: pnpm run test:coverage

      - run:
          name: Move coverage files if they exist
          command: |
            if [ -d "coverage" ]; then
              mkdir -p ~/build/coverage
              cp -r coverage ~/build/coverage/frontend || true
            fi
          when: always

      - persist_to_workspace:
          root: ~/build
          paths:
            - coverage

  # Backend tests with parallelization
  backend-tests:
    <<: *defaults
    parallelism: 18
    steps:
      - checkout
      - node/install:
          node-version: '24.13'
      - node/install-pnpm
      - node/install-packages:
          pkg-manager: pnpm
          override-ci-command: pnpm i

      - run:
          name: Add PostgreSQL repository and update
          command: |
            sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
            wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
            sudo apt-get update -y

      - run:
          name: Install System Dependencies
          command: |
            sudo apt-get update
            DEBIAN_FRONTEND=noninteractive sudo apt-get install -y \
              libpq-dev \
              redis-server \
              postgresql-common \
              postgresql-16 \
              postgresql-16-pgvector \
              build-essential \
              git \
              curl \
              libssl-dev \
              zlib1g-dev \
              libreadline-dev \
              libyaml-dev \
              openjdk-11-jdk \
              jq \
              software-properties-common \
              ca-certificates \
              imagemagick \
              libxml2-dev \
              libxslt1-dev \
              file \
              g++ \
              gcc \
              autoconf \
              gnupg2 \
              patch \
              ruby-dev \
              liblzma-dev \
              libgmp-dev \
              libncurses5-dev \
              libffi-dev \
              libgdbm6 \
              libgdbm-dev \
              libvips

      - run:
          name: Install RVM and Ruby 3.4.4
          command: |
            sudo apt-get install -y gpg
            gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
            \curl -sSL https://get.rvm.io | bash -s stable
            echo 'source ~/.rvm/scripts/rvm' >> $BASH_ENV
            source ~/.rvm/scripts/rvm
            rvm install "3.4.4"
            rvm use 3.4.4 --default
            gem install bundler -v 2.5.16

      - run:
          name: Install Application Dependencies
          command: |
            source ~/.rvm/scripts/rvm
            bundle install

      # Install and configure OpenSearch
      - run:
          name: Install OpenSearch
          command: |
            # Download and install OpenSearch 2.11.0 (compatible with Elasticsearch 7.x clients)
            wget https://artifacts.opensearch.org/releases/bundle/opensearch/2.11.0/opensearch-2.11.0-linux-x64.tar.gz
            tar -xzf opensearch-2.11.0-linux-x64.tar.gz
            sudo mv opensearch-2.11.0 /opt/opensearch

      - run:
          name: Configure and Start OpenSearch
          command: |
            # Configure OpenSearch for single-node testing
            cat > /opt/opensearch/config/opensearch.yml \<< EOF
            cluster.name: chatwoot-test
            node.name: node-1
            network.host: 0.0.0.0
            http.port: 9200
            discovery.type: single-node
            plugins.security.disabled: true
            EOF

            # Set ownership and permissions
            sudo chown -R $USER:$USER /opt/opensearch

            # Start OpenSearch in background
            /opt/opensearch/bin/opensearch -d -p /tmp/opensearch.pid

      - run:
          name: Wait for OpenSearch to be ready
          command: |
            echo "Waiting for OpenSearch to start..."
            for i in {1..30}; do
              if curl -s http://localhost:9200/_cluster/health | grep -q '"status"'; then
                echo "OpenSearch is ready!"
                exit 0
              fi
              echo "Waiting... ($i/30)"
              sleep 2
            done
            echo "OpenSearch failed to start"
            exit 1

      # Configure environment and database
      - run:
          name: Database Setup and Configure Environment Variables
          command: |
            pg_pass=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15 ; echo '')
            sed -i "s/REPLACE_WITH_PASSWORD/${pg_pass}/g" ${PWD}/.circleci/setup_chatwoot.sql
            chmod 644 ${PWD}/.circleci/setup_chatwoot.sql
            mv ${PWD}/.circleci/setup_chatwoot.sql /tmp/
            sudo -i -u postgres psql -f /tmp/setup_chatwoot.sql
            cp .env.example .env
            sed -i '/^FRONTEND_URL/d' .env
            sed -i -e '/REDIS_URL/ s/=.*/=redis:\/\/localhost:6379/' .env
            sed -i -e '/POSTGRES_HOST/ s/=.*/=localhost/' .env
            sed -i -e '/POSTGRES_USERNAME/ s/=.*/=chatwoot/' .env
            sed -i -e "/POSTGRES_PASSWORD/ s/=.*/=$pg_pass/" .env
            echo -en "\nINSTALLATION_ENV=circleci" >> ".env"
            echo -en "\nOPENSEARCH_URL=http://localhost:9200" >> ".env"

      # Database setup
      - run:
          name: Run DB migrations
          command: bundle exec rails db:chatwoot_prepare

      # Run backend tests (parallelized)
      - run:
          name: Run backend tests
          command: |
            mkdir -p ~/tmp/test-results/rspec
            mkdir -p ~/tmp/test-artifacts
            mkdir -p ~/build/coverage/backend

            # Use round-robin distribution (same as GitHub Actions) for better test isolation
            # This prevents tests with similar timing from being grouped on the same runner
            SPEC_FILES=($(find spec -name '*_spec.rb' | sort))
            TESTS=""

            for i in "${!SPEC_FILES[@]}"; do
              if [ $(( i % $CIRCLE_NODE_TOTAL )) -eq $CIRCLE_NODE_INDEX ]; then
                TESTS="$TESTS ${SPEC_FILES[$i]}"
              fi
            done

            bundle exec rspec -I ./spec --require coverage_helper --require spec_helper --format progress \
                              --format RspecJunitFormatter \
                              --out ~/tmp/test-results/rspec.xml \
                              -- $TESTS
          no_output_timeout: 30m

      # Store test results for better splitting in future runs
      - store_test_results:
          path: ~/tmp/test-results

      - run:
          name: Move coverage files if they exist
          command: |
            if [ -d "coverage" ]; then
              mkdir -p ~/build/coverage
              cp -r coverage ~/build/coverage/backend || true
            fi
          when: always

      - persist_to_workspace:
          root: ~/build
          paths:
            - coverage

  # Collect coverage from all jobs
  coverage:
    <<: *defaults
    steps:
      - checkout
      - attach_workspace:
          at: ~/build

      # Qlty coverage publish
      - qlty-orb/coverage_publish:
          files: |
            coverage/frontend/lcov.info

      - run:
          name: List coverage directory contents
          command: |
            ls -R ~/build/coverage || echo "No coverage directory"

      - store_artifacts:
          path: coverage
          destination: coverage

  build:
    <<: *defaults
    steps:
      - run:
          name: Legacy build aggregator
          command: |
            echo "All main jobs passed; build job kept only for GitHub required check compatibility."

workflows:
  version: 2
  build:
    jobs:
      - lint
      - frontend-tests
      - backend-tests
      - coverage:
          requires:
            - frontend-tests
            - backend-tests
      - build:
          requires:
            - lint
            - coverage
