name: ${COMPOSE_PROJECT_NAME}

services:
  # --- Infrastructure ---
  postgres:
    image: postgres:17-alpine
    container_name: ${COMPOSE_PROJECT_NAME}_db
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    ports:
      - "${DB_PORT}:5432"
    volumes:
      - db_data:/var/lib/postgresql/data
    networks: [app_net]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s

  redis:
    image: redis:7-alpine
    container_name: ${COMPOSE_PROJECT_NAME}_redis
    ports:
      - "${REDIS_PORT}:6379"
    networks: [app_net]

  clickhouse:
    image: clickhouse/clickhouse-server:latest
    container_name: ${COMPOSE_PROJECT_NAME}_clickhouse
    environment:
      - CLICKHOUSE_USER=baron
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
    ports:
      - "${CLICKHOUSE_PORT_HTTP}:8123"
      - "${CLICKHOUSE_PORT_NATIVE}:9000"
    volumes:
      - clickhouse_data:/var/lib/clickhouse
    networks: [app_net]

  # --- Ory Stack ---
  postgres_ory:
    image: postgres:${ORY_POSTGRES_TAG:-17-alpine}
    container_name: ${COMPOSE_PROJECT_NAME}_ory_db
    environment:
      - POSTGRES_USER=${ORY_POSTGRES_USER:-ory}
      - POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD}
      - POSTGRES_DB=${ORY_POSTGRES_DB:-ory}
    volumes:
      - ory_db_data:/var/lib/postgresql/data
      - ./ory/init-db:/docker-entrypoint-initdb.d:ro
    networks: [app_net]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${ORY_POSTGRES_USER:-ory} -d ${KRATOS_DB:-ory_kratos}"]
      interval: 5s

  kratos-migrate:
    image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
    env_file: .env
    environment:
      - DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
      - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
      - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
      - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
      - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/","${USERFRONT_URL}","${USERFRONT_URL}/","${USERFRONT_URL}/ko","${USERFRONT_URL}/ko/","${USERFRONT_URL}/en","${USERFRONT_URL}/en/","${USERFRONT_URL}/auth/callback","${USERFRONT_URL}/ko/auth/callback","${USERFRONT_URL}/en/auth/callback","${ADMINFRONT_URL}/auth/callback","${DEVFRONT_URL}/auth/callback","${ORGFRONT_URL}/auth/callback"]}
      - KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
      - KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
      - KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
      - KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
      - KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
      - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
      - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
    volumes:
      - ./config/.generated/ory/kratos:/etc/config/kratos:ro
    command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes
    networks: [app_net]
    depends_on:
      postgres_ory: { condition: service_healthy }

  kratos:
    image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
    container_name: ${COMPOSE_PROJECT_NAME}_kratos
    env_file: .env
    environment:
      - DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
      - COOKIE_SECRET=${COOKIE_SECRET}
      - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
      - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
      - KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}
      - KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["${KRATOS_UI_URL}","${KRATOS_UI_URL}/","${USERFRONT_URL}","${USERFRONT_URL}/","${USERFRONT_URL}/ko","${USERFRONT_URL}/ko/","${USERFRONT_URL}/en","${USERFRONT_URL}/en/","${USERFRONT_URL}/auth/callback","${USERFRONT_URL}/ko/auth/callback","${USERFRONT_URL}/en/auth/callback","${ADMINFRONT_URL}/auth/callback","${DEVFRONT_URL}/auth/callback","${ORGFRONT_URL}/auth/callback"]}
      - KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL}/error
      - KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL}/error?error=settings_disabled
      - KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL}/recovery
      - KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL}/verification
      - KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL}/login
      - KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL}/registration
      - KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL}/login
    volumes:
      - ./config/.generated/ory/kratos:/etc/config/kratos:ro
    command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
    networks: [app_net]
    depends_on:
      kratos-migrate: { condition: service_completed_successfully }

  hydra-migrate:
    image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
    env_file: .env
    environment:
      - DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
    command: migrate sql up -e --yes
    networks: [app_net]
    depends_on:
      postgres_ory: { condition: service_healthy }

  hydra:
    image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
    container_name: ${COMPOSE_PROJECT_NAME}_hydra
    env_file: .env
    environment:
      - DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
      - URLS_SELF_ISSUER=${HYDRA_PUBLIC_URL}
      - URLS_LOGIN=${HYDRA_LOGIN_URL:-${USERFRONT_URL}/login}
      - URLS_CONSENT=${HYDRA_CONSENT_URL:-${USERFRONT_URL}/consent}
      - URLS_ERROR=${HYDRA_ERROR_URL:-${USERFRONT_URL}/error}
      - SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD}
    volumes:
      - ./config/.generated/ory/hydra:/etc/config/hydra:ro
    command: serve -c /etc/config/hydra/hydra.yml all --dev
    networks: [app_net]
    depends_on:
      hydra-migrate: { condition: service_completed_successfully }

  keto-migrate:
    image: oryd/keto:${KETO_VERSION:-v26.2.0}
    env_file: .env
    environment:
      - DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
    volumes:
      - ./config/.generated/ory/keto:/etc/config/keto:ro
    command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"]
    networks: [app_net]
    depends_on:
      postgres_ory: { condition: service_healthy }

  keto:
    image: oryd/keto:${KETO_VERSION:-v26.2.0}
    container_name: ${COMPOSE_PROJECT_NAME}_keto
    env_file: .env
    environment:
      - DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
    volumes:
      - ./config/.generated/ory/keto:/etc/config/keto:ro
    command: serve -c /etc/config/keto/keto.yml
    networks: [app_net]
    depends_on:
      keto-migrate: { condition: service_completed_successfully }

  oathkeeper_logs_init:
    image: alpine:latest
    command: ["sh", "-c", "mkdir -p /var/log/oathkeeper && chown -R ${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001} /var/log/oathkeeper"]
    volumes:
      - oathkeeper_logs:/var/log/oathkeeper
    networks: [app_net]

  oathkeeper:
    image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v26.2.0}
    container_name: ${COMPOSE_PROJECT_NAME}_oathkeeper
    env_file: .env
    user: "${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001}"
    ports:
      - "${OATHKEEPER_PROXY_PORT}:4455"
    environment:
      - APP_ENV=${APP_ENV:-production}
      - LOG_LEVEL=debug
      - OATHKEEPER_INTROSPECT_CLIENT_ID=${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}
      - OATHKEEPER_INTROSPECT_CLIENT_SECRET=${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}
    volumes:
      - ./config/.generated/ory/oathkeeper:/etc/config/oathkeeper:ro
      - oathkeeper_logs:/var/log/oathkeeper
    entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"]
    networks: [app_net]
    depends_on:
      oathkeeper_logs_init: { condition: service_completed_successfully }
      kratos: { condition: service_started }
      hydra: { condition: service_started }

  ory_stack_check:
    image: alpine:latest
    container_name: ${COMPOSE_PROJECT_NAME}_ory_stack_check
    command: >
      /bin/sh -c "
        apk add --no-cache curl;
        echo 'Wait for Ory services...';
        check_ready() {
          name=\"$$1\";
          url=\"$$2\";
          max=\"$${ORY_STACK_CHECK_MAX_ATTEMPTS:-60}\";
          i=1;
          while [ \"$$i\" -le \"$$max\" ]; do
            if curl --connect-timeout 2 --max-time 3 -fsS \"$$url\" >/dev/null; then
              echo \"Ory service ready: $$name\";
              return 0;
            fi;
            echo \"Waiting for Ory service: $$name ($$i/$$max)\";
            i=$$((i + 1));
            sleep 1;
          done;
          echo \"ERROR: Ory service not ready: $$name after $$max attempts ($$url)\" >&2;
          echo \"ERROR: Check service logs: docker logs $${COMPOSE_PROJECT_NAME}_$$name\" >&2;
          return 1;
        };
        check_ready kratos http://kratos:4433/health/ready || exit 1;
        check_ready hydra http://hydra:4444/health/ready || exit 1;
        check_ready keto http://keto:4466/health/ready || exit 1;
        echo 'Ory stack is ready.';"
    depends_on:
      - kratos
      - hydra
      - keto
    networks: [app_net]

  init-rp:
    image: alpine:latest
    env_file: .env
    command:
      - /bin/sh
      - -ec
      - |
        apk add --no-cache curl tar
        HYDRA_CLI_VERSION="$${HYDRA_VERSION:-v26.2.0}"
        HYDRA_CLI_VERSION="$${HYDRA_CLI_VERSION%-distroless}"
        HYDRA_CLI_ARCHIVE_VERSION="$${HYDRA_CLI_VERSION#v}"
        curl -fsSLo /tmp/hydra.tar.gz "https://github.com/ory/hydra/releases/download/$${HYDRA_CLI_VERSION}/hydra_$${HYDRA_CLI_ARCHIVE_VERSION}-linux_64bit.tar.gz"
        tar -xzf /tmp/hydra.tar.gz -C /usr/local/bin hydra
        rm /tmp/hydra.tar.gz

        upsert_client() {
          ID=$$1
          shift
          if hydra get oauth2-client --endpoint "$${HYDRA_ADMIN_URL:-http://hydra:4445}" "$$ID" >/dev/null 2>&1; then
            hydra update oauth2-client --endpoint "$${HYDRA_ADMIN_URL:-http://hydra:4445}" "$$ID" "$$@"
          else
            hydra create oauth2-client --endpoint "$${HYDRA_ADMIN_URL:-http://hydra:4445}" --id "$$ID" "$$@"
          fi
        }

        upsert_client "adminfront" \
          --name "AdminFront" \
          --grant-type authorization_code,refresh_token \
          --response-type code \
          --scope openid,offline_access,profile,email \
          --token-endpoint-auth-method none \
          --redirect-uri "$${ADMINFRONT_CALLBACK_URLS:-$${ADMINFRONT_URL}/auth/callback}"

        upsert_client "devfront" \
          --name "DevFront" \
          --grant-type authorization_code,refresh_token \
          --response-type code \
          --scope openid,offline_access,profile,email \
          --token-endpoint-auth-method none \
          --redirect-uri "$${DEVFRONT_CALLBACK_URLS:-$${DEVFRONT_URL}/auth/callback}"

        upsert_client "orgfront" \
          --name "OrgFront" \
          --grant-type authorization_code,refresh_token \
          --response-type code \
          --scope openid,offline_access,profile,email \
          --token-endpoint-auth-method none \
          --redirect-uri "$${ORGFRONT_CALLBACK_URLS:-$${ORGFRONT_URL}/auth/callback}"

        upsert_client "$${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}" \
          --secret "$${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}" \
          --grant-type client_credentials \
          --response-type token \
          --scope openid,offline_access,profile,email
    depends_on:
      ory_stack_check: { condition: service_completed_successfully }
    networks: [app_net]

  # --- Application Services ---
  backend:
    image: baron-backend:latest
    container_name: ${COMPOSE_PROJECT_NAME}_backend
    env_file: .env
    environment:
      - PORT=${BACKEND_PORT}
      - APP_ENV=${APP_ENV:-production}
      - IDP_PROVIDER=${IDP_PROVIDER:-ory}
      - USERFRONT_URL=${USERFRONT_URL}
      - KRATOS_ADMIN_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
      - HYDRA_ADMIN_URL=${HYDRA_ADMIN_URL:-http://hydra:4445}
      - HYDRA_PUBLIC_URL=${HYDRA_PUBLIC_URL}
      - KETO_READ_URL=${KETO_READ_URL:-http://keto:4466}
      - KETO_WRITE_URL=${KETO_WRITE_URL:-http://keto:4467}
      - DB_HOST=postgres
      - REDIS_ADDR=redis:6379
      - CLICKHOUSE_HOST=clickhouse
      - SEED_TENANT_CSV_PATH=/app/seed-tenant.csv
    ports:
      - "${BACKEND_PORT}:${BACKEND_PORT}"
    volumes:
      - ../../adminfront/seed-tenant.csv:/app/seed-tenant.csv:ro
    networks: [app_net]
    depends_on:
      postgres: { condition: service_healthy }
      redis: { condition: service_started }
      oathkeeper: { condition: service_started }

  gateway:
    image: nginx:alpine
    container_name: ${COMPOSE_PROJECT_NAME}_gateway
    ports:
      - "${USERFRONT_PORT}:80"
    volumes:
      - ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro
    networks: [app_net]

  adminfront:
    image: node:20-alpine
    container_name: ${COMPOSE_PROJECT_NAME}_adminfront
    working_dir: /app
    env_file: .env
    ports:
      - "${ADMINFRONT_PORT}:5173"
    volumes:
      - ../../common:/common
      - ../../adminfront:/app
      - ./adminfront/vite.config.ts:/app/vite.config.ts:ro
      - ./adminfront/auth.ts:/app/src/lib/auth.ts:ro
    command: sh ./scripts/runtime-mode.sh
    networks: [app_net]

  devfront:
    image: node:20-alpine
    container_name: ${COMPOSE_PROJECT_NAME}_devfront
    working_dir: /app
    env_file: .env
    ports:
      - "${DEVFRONT_PORT}:5173"
    volumes:
      - ../../common:/common
      - ../../devfront:/app
      - ./devfront/vite.config.ts:/app/vite.config.ts:ro
      - ./devfront/auth.ts:/app/src/lib/auth.ts:ro
    command: sh ./scripts/runtime-mode.sh
    networks: [app_net]

  orgfront:
    image: node:20-alpine
    container_name: ${COMPOSE_PROJECT_NAME}_orgfront
    working_dir: /app
    env_file: .env
    ports:
      - "${ORGFRONT_PORT}:5175"
    volumes:
      - ../../common:/common
      - ../../orgfront:/app
      - ./orgfront/vite.config.ts:/app/vite.config.ts:ro
      - ./orgfront/auth.ts:/app/src/lib/auth.ts:ro
    command: sh ./scripts/runtime-mode.sh
    networks: [app_net]

networks:
  app_net:
    name: ${COMPOSE_PROJECT_NAME}_net

volumes:
  db_data:
    name: db_data_${INSTANCE_NAME}
  ory_db_data:
    name: ory_db_data_${INSTANCE_NAME}
  clickhouse_data:
    name: clickhouse_data_${INSTANCE_NAME}
  oathkeeper_logs:
    name: oathkeeper_logs_${INSTANCE_NAME}
