name: CI

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

# Cancel in-progress runs for the same branch/PR to save runner minutes.
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ---------------------------------------------------------------------------
  # LINT — parallel linting for all three services
  # ---------------------------------------------------------------------------
  python-lint:
    name: Lint Python (Ruff)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install Ruff
        run: pip install ruff

      - name: Ruff check
        run: ruff check backend/

      - name: Ruff format check
        run: ruff format --check backend/

  go-lint:
    name: Lint Go (golangci-lint)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: "1.25"

      - name: golangci-lint
        # golangci-lint doesn't support Go 1.25 yet — run vet as a stand-in
        run: cd poller && go vet ./...

  frontend-lint:
    name: Lint Frontend (ESLint + tsc)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: frontend/package-lock.json

      - name: Install dependencies
        working-directory: frontend
        run: npm ci

      - name: ESLint
        working-directory: frontend
        run: npx eslint .

      - name: TypeScript type check
        working-directory: frontend
        run: npx tsc --noEmit

  # ---------------------------------------------------------------------------
  # TEST — parallel test suites for all three services
  # ---------------------------------------------------------------------------
  backend-test:
    name: Test Backend (pytest)
    runs-on: ubuntu-latest

    services:
      postgres:
        image: timescale/timescaledb:latest-pg17
        env:
          POSTGRES_DB: tod_test
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      nats:
        image: nats:2-alpine
        ports:
          - 4222:4222
        options: >-
          --health-cmd "true"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    env:
      ENVIRONMENT: dev
      DATABASE_URL: "postgresql+asyncpg://postgres:postgres@localhost:5432/tod_test"
      SYNC_DATABASE_URL: "postgresql+psycopg2://postgres:postgres@localhost:5432/tod_test"
      APP_USER_DATABASE_URL: "postgresql+asyncpg://app_user:app_password@localhost:5432/tod_test"
      TEST_DATABASE_URL: "postgresql+asyncpg://postgres:postgres@localhost:5432/tod_test"
      TEST_APP_USER_DATABASE_URL: "postgresql+asyncpg://app_user:app_password@localhost:5432/tod_test"
      CREDENTIAL_ENCRYPTION_KEY: "LLLjnfBZTSycvL2U07HDSxUeTtLxb9cZzryQl0R9E4w="
      JWT_SECRET_KEY: "change-this-in-production-use-a-long-random-string"
      REDIS_URL: "redis://localhost:6379/0"
      NATS_URL: "nats://localhost:4222"

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: pip-${{ hashFiles('backend/pyproject.toml') }}
          restore-keys: pip-

      - name: Install backend dependencies
        working-directory: backend
        run: pip install -e ".[dev]"

      - name: Set up test database roles
        env:
          PGPASSWORD: postgres
        run: |
          # Create app_user role for RLS-enforced connections
          psql -h localhost -U postgres -d tod_test -c "
            CREATE ROLE app_user WITH LOGIN PASSWORD 'app_password' NOSUPERUSER NOCREATEDB NOCREATEROLE;
            GRANT CONNECT ON DATABASE tod_test TO app_user;
            GRANT USAGE ON SCHEMA public TO app_user;
            ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO app_user;
            ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO app_user;
          " || true

          # Create poller_user role
          psql -h localhost -U postgres -d tod_test -c "
            DO \$\$
            BEGIN
              IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'poller_user') THEN
                CREATE ROLE poller_user WITH LOGIN PASSWORD 'poller_password' NOSUPERUSER NOCREATEDB NOCREATEROLE;
              END IF;
            END
            \$\$;
            GRANT CONNECT ON DATABASE tod_test TO poller_user;
            GRANT USAGE ON SCHEMA public TO poller_user;
          " || true

      - name: Run backend tests
        working-directory: backend
        run: python -m pytest tests/ -x -v --tb=short

  poller-test:
    name: Test Go Poller
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: "1.25"

      - uses: actions/cache@v4
        with:
          path: ~/go/pkg/mod
          key: go-${{ hashFiles('poller/go.sum') }}
          restore-keys: go-

      - name: Run poller tests
        working-directory: poller
        run: go test ./... -v -count=1

  frontend-test:
    name: Test Frontend (Vitest)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: frontend/package-lock.json

      - name: Install dependencies
        working-directory: frontend
        run: npm ci

      - name: Run frontend tests
        working-directory: frontend
        run: npx vitest run

  # ---------------------------------------------------------------------------
  # BUILD — sequential Docker builds + Trivy scans (depends on lint + test)
  # ---------------------------------------------------------------------------
  build:
    name: Build & Scan Docker Images
    runs-on: ubuntu-latest
    needs: [python-lint, go-lint, frontend-lint, backend-test, poller-test, frontend-test]

    steps:
      - uses: actions/checkout@v4

      # Build and scan each image SEQUENTIALLY to avoid OOM.
      # Each multi-stage build (Go, Python/pip, Node/tsc) can peak at 1-2 GB.
      # Running them in parallel would exceed typical runner memory.

      - name: Build API image
        run: docker build -f infrastructure/docker/Dockerfile.api -t tod-api:ci .

      - name: Scan API image
        uses: aquasecurity/trivy-action@0.33.1
        continue-on-error: true
        with:
          image-ref: "tod-api:ci"
          format: "table"
          exit-code: "1"
          severity: "HIGH,CRITICAL"

      - name: Build Poller image
        run: docker build -f poller/Dockerfile -t tod-poller:ci ./poller

      - name: Scan Poller image
        uses: aquasecurity/trivy-action@0.33.1
        continue-on-error: true
        with:
          image-ref: "tod-poller:ci"
          format: "table"
          exit-code: "1"
          severity: "HIGH,CRITICAL"

      - name: Build Frontend image
        run: docker build -f infrastructure/docker/Dockerfile.frontend -t tod-frontend:ci .

      - name: Scan Frontend image
        uses: aquasecurity/trivy-action@0.33.1
        continue-on-error: true
        with:
          image-ref: "tod-frontend:ci"
          format: "table"
          exit-code: "1"
          severity: "HIGH,CRITICAL"
