##############################################################
# GMLM Platform — Horizontal Scaling Architecture
#
# This file documents the infrastructure configuration for
# scaling from single-server to multi-server deployments.
#
# Scaling tiers:
#   Tier 1: Single server          — up to ~500 concurrent users
#   Tier 2: App + dedicated DB     — up to ~5,000 concurrent users
#   Tier 3: Multi-app + read replica — up to ~25,000 concurrent users
#   Tier 4: Load balanced cluster   — 100,000+ concurrent users
#
##############################################################

# ── Tier 1: Default single-server deployment ─────────────────
# What deploy.sh creates. PHP-FPM + MySQL + Redis on one server.
# No changes from default configuration.
# CPU: 4 cores, RAM: 8GB minimum.

# ── Tier 2: App server + dedicated database server ───────────
# DB_HOST points to a separate MySQL server.
# Redis can still be local or a managed service (Redis Cloud, ElastiCache).
# PHP-FPM workers: ceil(RAM / 50MB per worker) — typically 20-40 workers.

# ── Tier 3: Multiple app servers + read replica ───────────────
# Requires:
# 1. Shared session store (Redis — already configured)
# 2. Shared file storage (S3 or NFS — configure FILESYSTEM_DISK=s3)
# 3. MySQL read replica (set DB_READ_HOST)
# 4. Load balancer (Nginx, HAProxy, or cloud LB)

# ── Tier 4: Full horizontal scaling ──────────────────────────
# docker-compose.scale.yml (below) configures this tier
##############################################################
version: '3.8'

# ── Tier 4: Load-balanced multi-app-server deployment ─────────
# Deploy with:
#   docker stack deploy -c docker-compose.scale.yml gmlm
# or
#   docker compose -f docker-compose.yml -f docker-compose.scale.yml up --scale app=5

services:

  # Load balancer — distributes traffic across app replicas
  load_balancer:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/lb.conf:/etc/nginx/nginx.conf:ro
      - ./docker/nginx/certs:/etc/nginx/certs:ro
    depends_on:
      - app
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure

  # App servers — stateless, can scale horizontally
  # Sessions in Redis ✓ | File storage in S3 ✓ | Cache in Redis ✓
  # All state is external — any app instance can serve any request
  app:
    image: gmlm-platform:${GMLM_VERSION:-latest}
    environment:
      APP_ENV:          production
      APP_URL:          ${APP_URL}
      FILESYSTEM_DISK:  s3
      SESSION_DRIVER:   redis
      CACHE_DRIVER:     redis
      QUEUE_CONNECTION: redis
      # No horizon in multi-server — run on dedicated worker nodes
      OCTANE_ENABLED:   ${OCTANE_ENABLED:-false}
    deploy:
      replicas: ${APP_REPLICAS:-3}
      update_config:
        # Blue-green-style rolling update: one at a time
        parallelism:   1
        delay:         30s
        order:         start-first  # Start new before stopping old = zero downtime
        failure_action:rollback
      restart_policy:
        condition:     on-failure
        max_attempts:  3
      resources:
        limits:
          cpus:   '2.0'
          memory: '1G'
        reservations:
          cpus:   '0.5'
          memory: '512M'
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost/api/v1/health"]
      interval: 10s
      timeout:  5s
      retries:  3
      start_period: 30s

  # Horizon workers — separate from app servers
  # No HTTP traffic, only queue processing
  horizon:
    image: gmlm-platform:${GMLM_VERSION:-latest}
    command: php artisan horizon
    environment:
      APP_ENV:          production
      APP_URL:          ${APP_URL}
      QUEUE_CONNECTION: redis
    deploy:
      replicas: ${HORIZON_REPLICAS:-2}
      restart_policy:
        condition: on-failure
      resources:
        limits:
          cpus:   '4.0'
          memory: '2G'

  # Scheduler — only ONE instance should run (not replicated)
  scheduler:
    image: gmlm-platform:${GMLM_VERSION:-latest}
    command: /bin/sh -c "while true; do php artisan schedule:run --no-interaction; sleep 60; done"
    environment:
      APP_ENV: production
    deploy:
      replicas: 1  # MUST be 1 — scheduler runs on exactly one node
      placement:
        constraints:
          - node.role == manager

  # MySQL — single primary with replica
  mysql_primary:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE:      ${DB_DATABASE}
      MYSQL_USER:          ${DB_USERNAME}
      MYSQL_PASSWORD:      ${DB_PASSWORD}
    volumes:
      - mysql_primary_data:/var/lib/mysql
    deploy:
      replicas:   1
      placement:
        constraints:
          - node.labels.db == primary

  mysql_replica:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD:     ${DB_ROOT_PASSWORD}
      MYSQL_REPLICATION_USER:  replicator
      MYSQL_REPLICATION_PASS:  ${DB_REPLICATION_PASS}
    volumes:
      - mysql_replica_data:/var/lib/mysql
      - ./docker/mysql/replica-init.sql:/docker-entrypoint-initdb.d/01-replica.sql:ro
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.db == replica

  # Redis Sentinel — high availability for Redis
  # Sentinel monitors Redis primary and auto-promotes a replica on failure
  redis_primary:
    image: redis:7-alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_primary_data:/data
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.redis == primary

  redis_replica:
    image: redis:7-alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} --replicaof redis_primary 6379 --masterauth ${REDIS_PASSWORD}
    volumes:
      - redis_replica_data:/data
    deploy:
      replicas: 2

  redis_sentinel:
    image: redis:7-alpine
    command: |
      redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./docker/redis/sentinel.conf:/etc/redis/sentinel.conf
    deploy:
      replicas: 3  # Odd number for quorum

volumes:
  mysql_primary_data:
  mysql_replica_data:
  redis_primary_data:
  redis_replica_data:
