services:
  # ======================
  # PostgreSQL Database
  # ======================
  postgres:
    image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest}
    container_name: mosaic-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-mosaic}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password}
      POSTGRES_DB: ${POSTGRES_DB:-mosaic}
      # Performance tuning
      POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
      POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
      POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./docker/postgres/init-scripts:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - mosaic-internal
    labels:
      - "com.mosaic.service=database"
      - "com.mosaic.description=PostgreSQL 17 with pgvector"

  # ======================
  # Valkey Cache
  # ======================
  valkey:
    image: valkey/valkey:8-alpine
    container_name: mosaic-valkey
    restart: unless-stopped
    command:
      - valkey-server
      - --maxmemory ${VALKEY_MAXMEMORY:-256mb}
      - --maxmemory-policy noeviction
      - --appendonly yes
    ports:
      - "${VALKEY_PORT:-6379}:6379"
    volumes:
      - valkey_data:/data
    healthcheck:
      test: ["CMD", "valkey-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - mosaic-internal
    labels:
      - "com.mosaic.service=cache"
      - "com.mosaic.description=Valkey Redis-compatible cache"

  # ======================
  # Authentik PostgreSQL
  # ======================
  # authentik-postgres:
  #   image: postgres:17.7-alpine3.22
  #   container_name: mosaic-authentik-postgres
  #   restart: unless-stopped
  #   environment:
  #     POSTGRES_USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
  #     POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
  #     POSTGRES_DB: ${AUTHENTIK_POSTGRES_DB:-authentik}
  #   volumes:
  #     - authentik_postgres_data:/var/lib/postgresql/data
  #   healthcheck:
  #     test: ["CMD-SHELL", "pg_isready -U ${AUTHENTIK_POSTGRES_USER:-authentik}"]
  #     interval: 10s
  #     timeout: 5s
  #     retries: 5
  #     start_period: 20s
  #   networks:
  #     - mosaic-internal
  #   profiles:
  #     - authentik
  #     - full
  #   labels:
  #     - "com.mosaic.service=auth-database"
  #     - "com.mosaic.description=Authentik PostgreSQL database"

  # ======================
  # Authentik Redis
  # ======================
  # authentik-redis:
  #   image: valkey/valkey:8-alpine
  #   container_name: mosaic-authentik-redis
  #   restart: unless-stopped
  #   command: valkey-server --save 60 1 --loglevel warning
  #   volumes:
  #     - authentik_redis_data:/data
  #   healthcheck:
  #     test: ["CMD", "valkey-cli", "ping"]
  #     interval: 10s
  #     timeout: 5s
  #     retries: 5
  #     start_period: 10s
  #   networks:
  #     - mosaic-internal
  #   profiles:
  #     - authentik
  #     - full
  #   labels:
  #     - "com.mosaic.service=auth-cache"
  #     - "com.mosaic.description=Authentik Redis cache"

  # ======================
  # Authentik Server
  # ======================
  # authentik-server:
  #   image: ghcr.io/goauthentik/server:2025.10.2
  #   container_name: mosaic-authentik-server
  #   restart: unless-stopped
  #   command: server
  #   environment:
  #     AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
  #     AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
  #     AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
  #     AUTHENTIK_POSTGRESQL__PORT: 5432
  #     AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
  #     AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
  #     AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
  #     AUTHENTIK_REDIS__HOST: authentik-redis
  #     AUTHENTIK_REDIS__PORT: 6379
  #     AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin}
  #     AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@localhost}
  #     AUTHENTIK_COOKIE_DOMAIN: ${AUTHENTIK_COOKIE_DOMAIN:-.localhost}
  #   ports:
  #     - "${AUTHENTIK_PORT_HTTP:-9000}:9000"
  #     - "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
  #   volumes:
  #     - authentik_media:/media
  #     - authentik_templates:/templates
  #   depends_on:
  #     authentik-postgres:
  #       condition: service_healthy
  #     authentik-redis:
  #       condition: service_healthy
  #   healthcheck:
  #     test:
  #       [
  #         "CMD",
  #         "wget",
  #         "--no-verbose",
  #         "--tries=1",
  #         "--spider",
  #         "http://localhost:9000/-/health/live/",
  #       ]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 3
  #     start_period: 90s
  #   networks:
  #     - mosaic-internal
  #     - mosaic-public
  #   profiles:
  #     - authentik
  #     - full
  #   labels:
  #     - "com.mosaic.service=auth-server"
  #     - "com.mosaic.description=Authentik OIDC server"
  #     # Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
  #     - "traefik.enable=${TRAEFIK_ENABLE:-false}"
  #     - "traefik.http.routers.mosaic-auth.rule=Host(`${MOSAIC_AUTH_DOMAIN:-auth.mosaic.local}`)"
  #     - "traefik.http.routers.mosaic-auth.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
  #     - "traefik.http.routers.mosaic-auth.tls=${TRAEFIK_TLS_ENABLED:-true}"
  #     - "traefik.http.services.mosaic-auth.loadbalancer.server.port=9000"
  #     - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
  #     # Let's Encrypt (if enabled)
  #     - "traefik.http.routers.mosaic-auth.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"

  # ======================
  # Authentik Worker
  # ======================
  # authentik-worker:
  #   image: ghcr.io/goauthentik/server:2025.10.2
  #   container_name: mosaic-authentik-worker
  #   restart: unless-stopped
  #   command: worker
  #   environment:
  #     AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
  #     AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
  #     AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
  #     AUTHENTIK_POSTGRESQL__PORT: 5432
  #     AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
  #     AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
  #     AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
  #     AUTHENTIK_REDIS__HOST: authentik-redis
  #     AUTHENTIK_REDIS__PORT: 6379
  #   volumes:
  #     - authentik_media:/media
  #     - authentik_certs:/certs
  #     - authentik_templates:/templates
  #   depends_on:
  #     authentik-postgres:
  #       condition: service_healthy
  #     authentik-redis:
  #       condition: service_healthy
  #   networks:
  #     - mosaic-internal
  #   profiles:
  #     - authentik
  #     - full
  #   labels:
  #     - "com.mosaic.service=auth-worker"
  #     - "com.mosaic.description=Authentik background worker"

  # ======================
  # Ollama (Optional AI Service)
  # ======================
  # ollama:
  #   image: ollama/ollama:latest
  #   container_name: mosaic-ollama
  #   restart: unless-stopped
  #   ports:
  #     - "${OLLAMA_PORT:-11434}:11434"
  #   volumes:
  #     - ollama_data:/root/.ollama
  #   healthcheck:
  #     test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 3
  #     start_period: 60s
  #   networks:
  #     - mosaic-internal
  #   profiles:
  #     - ollama
  #     - full
  #   labels:
  #     - "com.mosaic.service=ai"
  #     - "com.mosaic.description=Ollama LLM service"
  #   # Uncomment if you have GPU support
  #   # deploy:
  #   #   resources:
  #   #     reservations:
  #   #       devices:
  #   #         - driver: nvidia
  #   #           count: 1
  #   #           capabilities: [gpu]

  # ======================
  # OpenBao Secrets Management (Optional)
  # ======================
  openbao:
    image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest}
    container_name: mosaic-openbao
    restart: unless-stopped
    user: root
    ports:
      - "127.0.0.1:${OPENBAO_PORT:-8200}:8200"
    volumes:
      - openbao_data:/openbao/data
      - openbao_init:/openbao/init
    environment:
      VAULT_ADDR: http://0.0.0.0:8200
      SKIP_SETCAP: "true"
    entrypoint: ["dumb-init", "--"]
    command: ["bao", "server", "-config=/openbao/config/config.hcl"]
    cap_add:
      - IPC_LOCK
    healthcheck:
      test: ["CMD-SHELL", "nc -z 127.0.0.1 8200 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - mosaic-internal
    profiles:
      - openbao
      - full
    labels:
      - "com.mosaic.service=secrets"
      - "com.mosaic.description=OpenBao secrets management"

  openbao-init:
    image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest}
    container_name: mosaic-openbao-init
    restart: unless-stopped
    user: root
    volumes:
      - openbao_init:/openbao/init
    environment:
      VAULT_ADDR: http://openbao:8200
    command: ["/openbao/init.sh"]
    depends_on:
      openbao:
        condition: service_healthy
    networks:
      - mosaic-internal
    profiles:
      - openbao
      - full
    labels:
      - "com.mosaic.service=secrets-init"
      - "com.mosaic.description=OpenBao auto-initialization sidecar"

  # ======================
  # Traefik Reverse Proxy (Optional - Bundled Mode)
  # ======================
  # Enable with: COMPOSE_PROFILES=traefik-bundled or --profile traefik-bundled
  # Set TRAEFIK_MODE=bundled in .env
  # traefik:
  #   image: traefik:v3.2
  #   container_name: mosaic-traefik
  #   restart: unless-stopped
  #   command:
  #     - "--configFile=/etc/traefik/traefik.yml"
  #   ports:
  #     - "${TRAEFIK_HTTP_PORT:-80}:80"
  #     - "${TRAEFIK_HTTPS_PORT:-443}:443"
  #     - "${TRAEFIK_DASHBOARD_PORT:-8080}:8080"
  #   volumes:
  #     - /var/run/docker.sock:/var/run/docker.sock:ro
  #     - ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
  #     - ./docker/traefik/dynamic:/etc/traefik/dynamic:ro
  #     - traefik_letsencrypt:/letsencrypt
  #   environment:
  #     - TRAEFIK_ACME_EMAIL=${TRAEFIK_ACME_EMAIL:-}
  #   networks:
  #     - mosaic-public
  #   profiles:
  #     - traefik-bundled
  #     - full
  #   labels:
  #     - "com.mosaic.service=reverse-proxy"
  #     - "com.mosaic.description=Traefik reverse proxy and load balancer"
  #   healthcheck:
  #     test: ["CMD", "traefik", "healthcheck", "--ping"]
  #     interval: 30s
  #     timeout: 10s
  #     retries: 3
  #     start_period: 20s

  # ======================
  # Mosaic API
  # ======================
  api:
    image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest}
    container_name: mosaic-api
    restart: unless-stopped
    environment:
      NODE_ENV: production
      # API Configuration - PORT is what NestJS reads
      PORT: ${API_PORT:-3001}
      API_HOST: ${API_HOST:-0.0.0.0}
      # Database
      DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic}
      # Cache
      VALKEY_URL: redis://valkey:6379
      # Authentication
      OIDC_ENABLED: ${OIDC_ENABLED:-false}
      OIDC_ISSUER: ${OIDC_ISSUER}
      OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
      OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
      OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-http://localhost:3001/auth/oauth2/callback/authentik}
      # JWT
      JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret}
      JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
      # Better Auth
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
      BETTER_AUTH_URL: ${BETTER_AUTH_URL:-}
      # Encryption (required for federation credentials/private keys)
      ENCRYPTION_KEY: ${ENCRYPTION_KEY}
      # Ollama (optional)
      OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
      # OpenBao (optional)
      OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200}
      # Telemetry (task completion tracking & predictions)
      MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false}
      MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-http://telemetry-api:8000}
      MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-}
      MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-}
      MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false}
      # Frontend URLs (for CORS and auth redirects)
      NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
      NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
      TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-}
    volumes:
      - openbao_init:/openbao/init:ro
    ports:
      - "${API_PORT:-3001}:${API_PORT:-3001}"
    depends_on:
      postgres:
        condition: service_healthy
      valkey:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - mosaic-internal
      - mosaic-public
    labels:
      - "com.mosaic.service=api"
      - "com.mosaic.description=Mosaic NestJS API"
      # Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
      - "traefik.enable=${TRAEFIK_ENABLE:-false}"
      - "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaic.local}`)"
      - "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
      - "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}"
      - "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
      - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
      # Let's Encrypt (if enabled)
      - "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"

  # ======================
  # Mosaic Orchestrator
  # ======================
  orchestrator:
    image: git.mosaicstack.dev/mosaic/stack-orchestrator:${IMAGE_TAG:-latest}
    container_name: mosaic-orchestrator
    restart: unless-stopped
    # Run as non-root user (node:node, UID 1000)
    user: "1000:1000"
    environment:
      NODE_ENV: production
      # Orchestrator Configuration
      ORCHESTRATOR_PORT: 3001
      # Bind to all interfaces so the web container can reach it over Docker networking
      HOST: 0.0.0.0
      AI_PROVIDER: ${AI_PROVIDER:-ollama}
      # Valkey
      VALKEY_URL: redis://valkey:6379
      VALKEY_HOST: valkey
      VALKEY_PORT: 6379
      # Claude API (required only when AI_PROVIDER=claude)
      CLAUDE_API_KEY: ${CLAUDE_API_KEY}
      # Docker
      DOCKER_SOCKET: /var/run/docker.sock
      # Git
      GIT_USER_NAME: "Mosaic Orchestrator"
      GIT_USER_EMAIL: "orchestrator@mosaicstack.dev"
      # Security
      KILLSWITCH_ENABLED: true
      SANDBOX_ENABLED: true
      # API key for authenticating requests from the web proxy
      ORCHESTRATOR_API_KEY: ${ORCHESTRATOR_API_KEY}
    ports:
      - "3002:3001"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - orchestrator_workspace:/workspace
    depends_on:
      valkey:
        condition: service_healthy
      api:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - mosaic-internal
    # Security hardening
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    read_only: false # Cannot be read-only due to workspace writes
    tmpfs:
      - /tmp:noexec,nosuid,size=100m
    labels:
      - "com.mosaic.service=orchestrator"
      - "com.mosaic.description=Mosaic Agent Orchestrator"
      - "com.mosaic.security=hardened"
      - "com.mosaic.security.non-root=true"
      - "com.mosaic.security.capabilities=minimal"

  # ======================
  # Mosaic Web
  # ======================
  web:
    image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest}
    container_name: mosaic-web
    restart: unless-stopped
    environment:
      NODE_ENV: production
      PORT: ${WEB_PORT:-3000}
      NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
      # Server-side orchestrator proxy (API routes forward to orchestrator service)
      ORCHESTRATOR_URL: http://orchestrator:3001
      ORCHESTRATOR_API_KEY: ${ORCHESTRATOR_API_KEY}
    ports:
      - "${WEB_PORT:-3000}:${WEB_PORT:-3000}"
    depends_on:
      api:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - mosaic-internal
      - mosaic-public
    labels:
      - "com.mosaic.service=web"
      - "com.mosaic.description=Mosaic Next.js Web App"
      # Traefik labels (activated when TRAEFIK_MODE=bundled or upstream)
      - "traefik.enable=${TRAEFIK_ENABLE:-false}"
      - "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.local}`)"
      - "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
      - "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}"
      - "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
      - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
      # Let's Encrypt (if enabled)
      - "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"

# ======================
# Volumes
# ======================
volumes:
  postgres_data:
    name: mosaic-postgres-data
    driver: local
  valkey_data:
    name: mosaic-valkey-data
    driver: local
  # authentik_postgres_data:
  #   name: mosaic-authentik-postgres-data
  #   driver: local
  # authentik_redis_data:
  #   name: mosaic-authentik-redis-data
  #   driver: local
  # authentik_media:
  #   name: mosaic-authentik-media
  #   driver: local
  # authentik_certs:
  #   name: mosaic-authentik-certs
  #   driver: local
  # authentik_templates:
  #   name: mosaic-authentik-templates
  #   driver: local
  # ollama_data:
  #   name: mosaic-ollama-data
  #   driver: local
  openbao_data:
    name: mosaic-openbao-data
    driver: local
  openbao_init:
    name: mosaic-openbao-init
    driver: local
  # traefik_letsencrypt:
  #   name: mosaic-traefik-letsencrypt
  #   driver: local
  orchestrator_workspace:
    name: mosaic-orchestrator-workspace
    driver: local

# ======================
# Networks
# ======================
networks:
  # Internal network for database/cache isolation
  # Note: NOT marked as 'internal: true' because API needs external access
  # for Authentik OIDC and external Ollama services
  mosaic-internal:
    name: mosaic-internal
    driver: bridge
  # Public network for services that need external access
  mosaic-public:
    name: mosaic-public
    driver: bridge
