# ═══════════════════════════════════════════════════════════════════════
#  zm-rag — Production Docker Compose
#  Usage:
#    cp .env.prod.example .env.prod && vim .env.prod
#    docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build
# ═══════════════════════════════════════════════════════════════════════
name: zm-rag-prod

x-logging: &default-logging
  driver: json-file
  options:
    max-size: "50m"
    max-file: "3"

x-backend-common: &backend-common
  build:
    context: ..
    dockerfile: docker/backend/Dockerfile.prod
  env_file:
    - ./backend/.env.docker
  environment:
    - LLM_BASE_URL=${LLM_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
    - LLM_API_KEY=${LLM_API_KEY:-}
    - LLM_MODEL=${LLM_MODEL:-qwen3.5-flash}
    - LLM_ENABLE_THINKING=${LLM_ENABLE_THINKING:-false}
    - EMBEDDING_BASE_URL=${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
    - EMBEDDING_API_KEY=${EMBEDDING_API_KEY:-}
    - EMBEDDING_MODEL=${EMBEDDING_MODEL:-text-embedding-v4}
    - JWT_SECRET=${JWT_SECRET:-zm-rag-prod-secret-CHANGE-ME}
    - CORS_ORIGINS=${CORS_ORIGINS:-["http://localhost:10038"]}
    - UVICORN_WORKERS=${UVICORN_WORKERS:-4}
    # HuggingFace mirror for China mainland (Docling model downloads)
    - HF_ENDPOINT=https://hf-mirror.com
    - HF_HOME=/home/app/zm-rag/hf_cache
  volumes:
    - ${FILE_STORAGE_PATH:-/home/app/zm-rag/data/files}:/data/files
    - /home/app/zm-rag/hf_cache:/home/app/zm-rag/hf_cache
  networks:
    - zm-rag-net
  restart: unless-stopped
  logging: *default-logging

services:
  # ── Infrastructure ──────────────────────────────────────────────────

  opensearch:
    build:
      context: ./opensearch
      dockerfile: Dockerfile
    container_name: zm-rag-prod-opensearch
    environment:
      - OPENSEARCH_JAVA_OPTS=${OPENSEARCH_JAVA_OPTS:--Xms4g -Xmx4g}
      - cluster.name=zm-rag-cluster
      - node.name=zm-rag-node-1
      - discovery.type=single-node
      - plugins.security.disabled=true
      - network.host=0.0.0.0
      - knn.plugin.enabled=true
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch_data:/usr/share/opensearch/data
      - ./opensearch/analysis:/usr/share/opensearch/config/analysis
      - ./opensearch/opensearch.yml:/usr/share/opensearch/config/opensearch.yml:ro
    networks:
      - zm-rag-net
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health | grep -qE '\"status\":\"(green|yellow)\"' || exit 1"]
      interval: 30s
      timeout: 15s
      retries: 10
      start_period: 60s

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:2.19.4
    container_name: zm-rag-prod-dashboards
    depends_on:
      opensearch:
        condition: service_healthy
    environment:
      - OPENSEARCH_HOSTS=["http://opensearch:9200"]
      - DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
    ports:
      - "5601:5601"
    networks:
      - zm-rag-net
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:5601/api/status | grep -q 'available' || exit 1"]
      interval: 30s
      timeout: 15s
      retries: 10
      start_period: 120s

  neo4j:
    image: neo4j:5-community
    container_name: zm-rag-prod-neo4j
    environment:
      - NEO4J_AUTH=neo4j/${NEO4J_PASSWORD:-zm_rag_2024}
      - NEO4J_PLUGINS=["apoc"]
      - NEO4J_server_memory_heap_initial__size=${NEO4J_HEAP_INIT:-2g}
      - NEO4J_server_memory_heap_max__size=${NEO4J_HEAP_MAX:-4g}
      - NEO4J_server_memory_pagecache_size=${NEO4J_PAGECACHE:-2g}
      - NEO4J_dbms_security_procedures_unrestricted=apoc.*
      - NEO4J_dbms_security_procedures_allowlist=apoc.*
    volumes:
      - neo4j_data:/data
      - neo4j_logs:/logs
    networks:
      - zm-rag-net
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD-SHELL", "cypher-shell -u neo4j -p ${NEO4J_PASSWORD:-zm_rag_2024} 'RETURN 1' || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s

  redis:
    image: redis:alpine
    container_name: zm-rag-prod-redis
    command: >
      redis-server
        --maxmemory ${REDIS_MAXMEMORY:-4gb}
        --maxmemory-policy allkeys-lru
        --appendonly yes
        --save 900 1
        --save 300 10
    volumes:
      - redis_data:/data
    networks:
      - zm-rag-net
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  mysql:
    image: mysql:8.0
    container_name: zm-rag-prod-mysql
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-zm123456}
      - MYSQL_DATABASE=zm_rag
      - MYSQL_CHARSET=utf8mb4
      - TZ=Asia/Shanghai
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --default-time-zone=+08:00
      - --max-connections=200
      - --innodb-buffer-pool-size=1G
      - --slow-query-log=ON
      - --long-query-time=2
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - zm-rag-net
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-zm123456}"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

  # ── Document Converter (Java / Spire.Office) ────────────────────────

  doc-converter:
    build:
      context: ../converter
      dockerfile: Dockerfile
    container_name: zm-rag-prod-doc-converter
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/zm_rag?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=${MYSQL_ROOT_PASSWORD:-zm123456}
    volumes:
      - ${FILE_STORAGE_PATH:-/home/app/zm-rag/data/files}:/data/files
    networks:
      - zm-rag-net
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:18800/actuator/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

  # ── Backend API (FastAPI + Uvicorn) ────────────────────────────────

  backend:
    <<: *backend-common
    container_name: zm-rag-prod-backend
    ports:
      - "8900:8900"
    command: >
      uvicorn app.main:app
        --host 0.0.0.0
        --port 8900
        --workers ${UVICORN_WORKERS:-4}
        --timeout-keep-alive 65
        --access-log
    depends_on:
      opensearch:
        condition: service_healthy
      neo4j:
        condition: service_healthy
      redis:
        condition: service_healthy
      mysql:
        condition: service_healthy
      doc-converter:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:8900/health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s

  # ── Celery Worker ──────────────────────────────────────────────────

  celery-worker:
    <<: *backend-common
    container_name: zm-rag-prod-celery-worker
    user: root
    command: >
      celery -A app.tasks.celery_app:celery_app worker
        --loglevel=info
        --pool=solo
        --concurrency=1
    depends_on:
      opensearch:
        condition: service_healthy
      neo4j:
        condition: service_healthy
      redis:
        condition: service_healthy
      doc-converter:
        condition: service_healthy

  # ── Frontend (Vue + Nginx reverse proxy) ───────────────────────────

  frontend:
    build:
      context: ..
      dockerfile: docker/frontend/Dockerfile.prod
    container_name: zm-rag-prod-frontend
    depends_on:
      backend:
        condition: service_healthy
    ports:
      - "10038:10038"
    networks:
      - zm-rag-net
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:10038/nginx-health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 10s

volumes:
  opensearch_data:
    driver: local
  neo4j_data:
    driver: local
  neo4j_logs:
    driver: local
  redis_data:
    driver: local
  mysql_data:
    driver: local

networks:
  zm-rag-net:
    driver: bridge
