name: pgclone CI

on:
  push:
    branches: [ main, 'fix/**', 'feature/**' ]
  pull_request:
    branches: [ main ]

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
  test:
    name: "PostgreSQL ${{ matrix.pg_version }}"
    runs-on: ubuntu-22.04
    strategy:
      fail-fast: false
      matrix:
        pg_version: [14, 15, 16, 17, 18]

    services:
      source-db:
        image: postgres:${{ matrix.pg_version }}
        env:
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: source_db
        ports:
          - 5433:5432
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 5s
          --health-timeout 5s
          --health-retries 10

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

      - name: Install PostgreSQL ${{ matrix.pg_version }}
        run: |
          PG_VER=${{ matrix.pg_version }}
          sudo apt-get update -qq
          sudo apt-get install -y postgresql-common ca-certificates curl gnupg
          sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
          sudo apt-get update -qq
          if ! sudo apt-get install -y postgresql-$PG_VER postgresql-server-dev-$PG_VER libpq-dev build-essential; then
            echo "Primary install failed for PG${PG_VER}. Trying fallback..."
            apt-cache policy "postgresql-$PG_VER" || true
            sudo apt-get install -y "postgresql-$PG_VER" postgresql-server-dev-all libpq-dev build-essential
          fi
          /usr/lib/postgresql/$PG_VER/bin/pg_config --version || pg_config --version

      - name: Build and install pgclone
        run: |
          PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config
          make PG_CONFIG=$PG_CONFIG CFLAGS="-Isrc"
          sudo make install PG_CONFIG=$PG_CONFIG

      - name: Start target PostgreSQL
        run: |
          PG_VER=${{ matrix.pg_version }}
          PGBIN=/usr/lib/postgresql/$PG_VER/bin
          PGDATA="$RUNNER_TEMP/pg_target_$PG_VER"
          PGLOG="$RUNNER_TEMP/pg_target_$PG_VER.log"

          sudo systemctl stop postgresql 2>/dev/null || true
          rm -rf "$PGDATA"
          "$PGBIN/initdb" -D "$PGDATA" --username=postgres --auth=trust

          {
            echo "port = 5434"
            echo "listen_addresses = 'localhost'"
            echo "shared_buffers = '128MB'"
            echo "max_connections = 50"
            echo "huge_pages = off"
            echo "shared_preload_libraries = 'pgclone'"
            echo "max_worker_processes = 32"
            echo "unix_socket_directories = '/tmp'"
            echo "logging_collector = off"
          } >> "$PGDATA/postgresql.conf"

          if ! "$PGBIN/pg_ctl" -D "$PGDATA" -l "$PGLOG" -w -t 30 start; then
            echo "=== PostgreSQL failed to start ==="
            cat "$PGLOG"
            exit 1
          fi

          "$PGBIN/createdb" -h localhost -p 5434 -U postgres target_db

      - name: Seed source database
        run: |
          PGPASSWORD=testpass psql -h localhost -p 5433 -U postgres -d source_db \
            -f test/fixtures/seed.sql

      - name: Install pgTAP and create extensions
        run: |
          PG_CONFIG=/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config
          git clone --depth 1 https://github.com/theory/pgtap.git /tmp/pgtap
          cd /tmp/pgtap && make PG_CONFIG=$PG_CONFIG && sudo make install PG_CONFIG=$PG_CONFIG

          psql -h localhost -p 5434 -U postgres -d target_db <<SQL
            CREATE EXTENSION IF NOT EXISTS pgtap;
            CREATE EXTENSION IF NOT EXISTS pgclone;
            SELECT pgclone_version();
          SQL

          psql -h localhost -p 5434 -U postgres -d target_db \
            -c "ALTER DATABASE target_db SET app.source_conninfo = 'host=localhost port=5433 dbname=source_db user=postgres password=testpass';"

      - name: "Test: pgTAP (33 sync tests)"
        run: |
          psql -h localhost -p 5434 -U postgres -d target_db \
            -f test/pgclone_test.sql

      - name: "Test: pgclone_database_create"
        run: |
          SOURCE_CONNINFO="host=localhost port=5433 dbname=source_db user=postgres password=testpass"

          psql -h localhost -p 5434 -U postgres -d postgres \
            -c "CREATE EXTENSION IF NOT EXISTS pgclone;"
          psql -h localhost -p 5434 -U postgres -d postgres \
            -c "DROP DATABASE IF EXISTS clone_test_db;" 2>/dev/null || true

          # Create and clone
          psql -h localhost -p 5434 -U postgres -d postgres -v ON_ERROR_STOP=1 \
            -c "SELECT pgclone_database_create('${SOURCE_CONNINFO}', 'clone_test_db', true);"

          # Verify
          DB_EXISTS=$(psql -h localhost -p 5434 -U postgres -d postgres -tAc \
            "SELECT 1 FROM pg_database WHERE datname = 'clone_test_db'")
          [ "$DB_EXISTS" = "1" ] || { echo "FAIL: DB not created"; exit 1; }

          ROW_COUNT=$(psql -h localhost -p 5434 -U postgres -d clone_test_db -tAc \
            "SELECT count(*) FROM test_schema.customers")
          [ "$ROW_COUNT" -ge 1 ] || { echo "FAIL: no data"; exit 1; }

          # Idempotent re-clone
          psql -h localhost -p 5434 -U postgres -d postgres -v ON_ERROR_STOP=1 \
            -c "SELECT pgclone_database_create('${SOURCE_CONNINFO}', 'clone_test_db', false);"

          # Cleanup
          psql -h localhost -p 5434 -U postgres -d postgres \
            -c "DROP DATABASE IF EXISTS clone_test_db;" 2>/dev/null || true

          echo "ALL database_create tests PASSED"

      - name: "Test: Async functions (bgworker)"
        run: |
          SOURCE_CONNINFO="host=localhost port=5433 dbname=source_db user=postgres password=testpass"
          PG="psql -h localhost -p 5434 -U postgres -d target_db"
          PASS=0; FAIL=0

          run_test() {
            if eval "$2" 2>/dev/null; then
              echo "  PASS: $1"; PASS=$((PASS + 1))
            else
              echo "  FAIL: $1"; FAIL=$((FAIL + 1))
            fi
          }

          # Clean slate — drop leftover tables from sync tests
          $PG -c "SELECT pgclone_clear_jobs();" 2>/dev/null || true
          $PG <<SQL 2>/dev/null
            DROP TABLE IF EXISTS public.simple_test CASCADE;
            DROP TABLE IF EXISTS public.simple_test_copy CASCADE;
            DROP TABLE IF EXISTS public.simple_test_empty CASCADE;
            DROP TABLE IF EXISTS public.async_renamed CASCADE;
            DROP TABLE IF EXISTS test_schema.customers_lite CASCADE;
            DROP TABLE IF EXISTS test_schema.active_only CASCADE;
          SQL

          # 1: table_async basic
          echo "---- Async: table_async basic ----"
          JOB=$($PG -tAc "SELECT pgclone_table_async('${SOURCE_CONNINFO}', 'public', 'simple_test', true);")
          run_test "returns job_id" "[ -n '$JOB' ] && [ '$JOB' -gt 0 ]"
          for i in $(seq 1 30); do
            S=$($PG -tAc "SELECT status FROM pgclone_jobs_view WHERE job_id=$JOB;" 2>/dev/null | tr -d '[:space:]')
            [ "$S" = "completed" ] || [ "$S" = "failed" ] && break; sleep 1
          done
          run_test "job completed" "[ '$S' = 'completed' ]"
          RC=$($PG -tAc "SELECT count(*)::int FROM public.simple_test;" 2>/dev/null | tr -d '[:space:]')
          run_test "simple_test has 5 rows" "[ '$RC' = '5' ]"

          # 2: table_async with target name
          echo "---- Async: table_async target name ----"
          JOB2=$($PG -tAc "SELECT pgclone_table_async('${SOURCE_CONNINFO}', 'public', 'simple_test', true, 'async_renamed');")
          for i in $(seq 1 30); do
            S2=$($PG -tAc "SELECT status FROM pgclone_jobs_view WHERE job_id=$JOB2;" 2>/dev/null | tr -d '[:space:]')
            [ "$S2" = "completed" ] || [ "$S2" = "failed" ] && break; sleep 1
          done
          run_test "renamed job completed" "[ '$S2' = 'completed' ]"
          TE=$($PG -tAc "SELECT count(*) FROM pg_tables WHERE schemaname='public' AND tablename='async_renamed';" 2>/dev/null | tr -d '[:space:]')
          run_test "async_renamed exists" "[ '$TE' = '1' ]"

          # 3: schema_async
          echo "---- Async: schema_async ----"
          $PG -c "DROP SCHEMA IF EXISTS test_schema CASCADE;" 2>/dev/null || true
          JOB3=$($PG -tAc "SELECT pgclone_schema_async('${SOURCE_CONNINFO}', 'test_schema', true);")
          for i in $(seq 1 60); do
            S3=$($PG -tAc "SELECT status FROM pgclone_jobs_view WHERE job_id=$JOB3;" 2>/dev/null | tr -d '[:space:]')
            [ "$S3" = "completed" ] || [ "$S3" = "failed" ] && break; sleep 1
          done
          run_test "schema_async completed" "[ '$S3' = 'completed' ]"
          CC=$($PG -tAc "SELECT count(*)::int FROM test_schema.customers;" 2>/dev/null | tr -d '[:space:]')
          run_test "customers has 10 rows" "[ '$CC' = '10' ]"

          # 4: progress/jobs/view
          echo "---- Async: progress & jobs ----"
          PR=$($PG -tAc "SELECT pgclone_progress($JOB);" 2>/dev/null)
          run_test "progress returns JSON" "echo '$PR' | grep -q 'job_id'"
          JJ=$($PG -tAc "SELECT pgclone_jobs();" 2>/dev/null)
          run_test "jobs returns JSON" "echo '$JJ' | grep -q 'job_id'"
          VC=$($PG -tAc "SELECT count(*) FROM pgclone_jobs_view;" 2>/dev/null | tr -d '[:space:]')
          run_test "jobs_view has rows" "[ '$VC' -ge 1 ]"

          # 5: clear_jobs
          echo "---- Async: clear_jobs ----"
          CL=$($PG -tAc "SELECT pgclone_clear_jobs();" 2>/dev/null | tr -d '[:space:]')
          run_test "clear_jobs works" "[ '$CL' -ge 1 ]"

          echo ""
          echo "============================================"
          echo "ASYNC: $PASS passed, $FAIL failed"
          echo "============================================"
          [ $FAIL -eq 0 ] || exit 1

      - name: Show logs on failure
        if: failure()
        run: |
          PG_VER=${{ matrix.pg_version }}
          PGLOG="$RUNNER_TEMP/pg_target_$PG_VER.log"
          echo "=== PostgreSQL $PG_VER log ==="
          [ -f "$PGLOG" ] && tail -150 "$PGLOG" || echo "No log found"
          echo "=== Port usage ==="
          sudo ss -tlnp | grep -E '5432|5433|5434' || true
