services:
    postgres:
        image: postgres:17-alpine
        container_name: baron_postgres
        environment:
            POSTGRES_USER: "${DB_USER:-baron}"
            POSTGRES_PASSWORD: "${DB_PASSWORD:-password}"
            POSTGRES_DB: "${DB_NAME:-baron_sso}"
        ports:
            - "${DB_PORT:-5432}:5432"
        volumes:
            - postgres_data:/var/lib/postgresql/data
            - ./docker/init-metadata:/docker-entrypoint-initdb.d
        networks:
            - baron_net
        healthcheck:
            test:
                [
                    "CMD-SHELL",
                    "pg_isready -U ${DB_USER:-baron} -d ${DB_NAME:-baron_sso}",
                ]
            interval: 5s
            timeout: 5s
            retries: 5
        restart: always

    clickhouse:
        image: clickhouse/clickhouse-server:latest
        container_name: baron_clickhouse
        restart: always
        volumes:
            - clickhouse_data:/var/lib/clickhouse
        environment:
            CLICKHOUSE_USER: "${CLICKHOUSE_USER:-baron}"
            CLICKHOUSE_PASSWORD: "${CLICKHOUSE_PASSWORD:-password}"
        networks:
            - baron_net
        healthcheck:
            test: ["CMD", "clickhouse-client", "--query", "SELECT 1"]
            interval: 5s
            timeout: 5s
            retries: 5

    redis:
        image: redis:7-alpine
        container_name: baron_redis
        restart: always
        command: redis-server --port 6389
        ports:
            - "6389:6389"
        volumes:
            - redis_data:/data
        networks:
            - baron_net
        healthcheck:
            test: ["CMD", "redis-cli", "-p", "6389", "ping"]
            interval: 5s
            timeout: 5s
            retries: 5

    gateway:
        image: nginx:alpine
        container_name: baron_gateway
        restart: always
        ports:
            - "${USERFRONT_PORT:-5000}:5000"
        volumes:
            - ./gateway/nginx.conf:/etc/nginx/conf.d/default.conf:ro
        networks:
            - baron_net
            - public_net
        healthcheck:
            test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
            interval: 10s
            timeout: 5s
            retries: 3
            start_period: 10s

    postgres_ory:
        image: postgres:${ORY_POSTGRES_TAG:-17-alpine}
        container_name: ory_postgres
        environment:
            - POSTGRES_USER=${ORY_POSTGRES_USER:-ory}
            - POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD:-secret}
            - POSTGRES_DB=${ORY_POSTGRES_DB:-ory}
        volumes:
            - ./docker/ory/init-db:/docker-entrypoint-initdb.d
            - ory_postgres_data:/var/lib/postgresql/data
        networks:
            - ory-net
        healthcheck:
            test:
                [
                    "CMD-SHELL",
                    "pg_isready -U ${ORY_POSTGRES_USER:-ory} -d ${KRATOS_DB:-ory_kratos}",
                ]
            interval: 5s
            timeout: 5s
            retries: 5

    kratos-migrate:
        image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
        environment:
            - DSN=postgres://${ORY_POSTGRES_USER}:${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}
            - 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
        command: migrate sql up -e -c /etc/config/kratos/kratos.yml --yes
        depends_on:
            postgres_ory:
                condition: service_healthy
        networks:
            - ory-net

    kratos:
        image: oryd/kratos:${KRATOS_VERSION:-v26.2.0}
        container_name: ory_kratos
        environment:
            - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
            - COOKIE_SECRET=${COOKIE_SECRET:-localcookie123}
            - KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL}
            - KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL}
            - 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
        command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
        depends_on:
            kratos-migrate:
                condition: service_completed_successfully
        networks:
            - ory-net
            - kratosnet

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

    hydra:
        image: oryd/hydra:${HYDRA_VERSION:-v26.2.0}
        container_name: ory_hydra
        environment:
            - DSN=postgres://${ORY_POSTGRES_USER}:${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
        command: serve -c /etc/config/hydra/hydra.yml all --dev
        depends_on:
            hydra-migrate:
                condition: service_completed_successfully
        networks:
            - ory-net
            - hydranet

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

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

    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:
            - ory-net

    oathkeeper:
        image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v0.40.6}
        container_name: oathkeeper
        restart: unless-stopped
        depends_on:
            oathkeeper_logs_init:
                condition: service_completed_successfully
            kratos:
                condition: service_started
        user: "${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001}"
        environment:
            - APP_ENV=${APP_ENV:-development}
            - 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
            - oathkeeper_logs:/var/log/oathkeeper
        entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"]
        networks:
            - ory-net
            - baron_net
            - public_net
        ports:
            - "4455:4455"
            - "4456:4456"
        healthcheck:
            test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4456/health/ready"]
            interval: 5s
            timeout: 5s
            retries: 5

    ory_clickhouse:
        image: clickhouse/clickhouse-server:latest
        container_name: ory_clickhouse
        environment:
            - CLICKHOUSE_USER=${ORY_CLICKHOUSE_USER:-ory}
            - CLICKHOUSE_PASSWORD=${ORY_CLICKHOUSE_PASSWORD:-orypass}
        volumes:
            - ory_clickhouse_data:/var/lib/clickhouse
            - ./docker/ory/clickhouse:/docker-entrypoint-initdb.d
        networks:
            - ory-net

    ory_stack_check:
        image: alpine:latest
        container_name: ory_stack_check
        command: >
            /bin/sh -c "
              apk add --no-cache curl;
              echo 'Wait for 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 ory_$$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 fully operational!';"
        depends_on:
            - kratos
            - hydra
            - keto
        networks:
            - ory-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

                # Function to create or update OAuth2 client (Idempotency)
                upsert_client() {
                  ID=$$1
                  shift
                  if hydra get oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" "$$ID" >/dev/null 2>&1; then
                    echo "Updating existing client: $$ID"
                    hydra update oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" "$$ID" "$$@"
                  else
                    echo "Creating new client: $$ID"
                    hydra create oauth2-client --endpoint "$${HYDRA_ADMIN_URL}" --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:
            - hydranet

    backend:
        build:
            context: ./backend
            dockerfile: Dockerfile
        container_name: baron_backend
        env_file:
            - .env
        environment:
            - APP_ENV=${APP_ENV:-development}
            - GO_ENV=${APP_ENV:-development}
            - COOKIE_SECRET=${COOKIE_SECRET}
            - JWT_SECRET=${JWT_SECRET}
            - NAVER_CLOUD_ACCESS_KEY=${NAVER_CLOUD_ACCESS_KEY}
            - NAVER_CLOUD_SECRET_KEY=${NAVER_CLOUD_SECRET_KEY}
            - NAVER_CLOUD_SERVICE_ID=${NAVER_CLOUD_SERVICE_ID}
            - NAVER_SENDER_PHONE_NUMBER=${NAVER_SENDER_PHONE_NUMBER}
            - USERFRONT_URL=${USERFRONT_URL}
            - REDIS_ADDR=${REDIS_ADDR}
            - IDP_PROVIDER=${IDP_PROVIDER:-ory}
            - KRATOS_ADMIN_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
            - HYDRA_ADMIN_URL=${HYDRA_ADMIN_URL:-http://hydra:4445}
            - HYDRA_PUBLIC_URL=${HYDRA_PUBLIC_URL}
            - DB_HOST=postgres
            - CLICKHOUSE_HOST=clickhouse
            - CLICKHOUSE_PORT=${CLICKHOUSE_PORT_NATIVE:-9000}
            - CLICKHOUSE_USER=${CLICKHOUSE_USER:-baron}
            - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-password}
            - SEED_TENANT_CSV_PATH=/app/seed-tenant.csv
        depends_on:
            clickhouse:
                condition: service_healthy
            redis:
                condition: service_healthy
            oathkeeper:
                condition: service_healthy
            kratos:
                condition: service_started
            hydra:
                condition: service_started
            keto:
                condition: service_started
            infra_check:
                condition: service_started
        networks:
            - baron_net
            - ory-net
        volumes:
            - ./backend:/app
            - ./adminfront/seed-tenant.csv:/app/seed-tenant.csv:ro
        command: ["go", "run", "./cmd/server"]
        healthcheck:
            test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
            interval: 10s
            timeout: 5s
            retries: 3
            start_period: 10s

    adminfront:
        build:
            context: ./adminfront
            dockerfile: Dockerfile
        container_name: baron_adminfront
        env_file:
            - .env
        environment:
            - APP_ENV=${APP_ENV:-stage}
            - API_PROXY_TARGET=http://baron_backend:3000
        ports:
            - "${ADMINFRONT_PORT:-5173}:5173"
        volumes:
            - ./adminfront:/app
            - ./common:/common
            - ./locales:/locales
            - /app/node_modules
        networks:
            - baron_net
        healthcheck:
            test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:5173/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
            interval: 10s
            timeout: 5s
            retries: 12
            start_period: 90s

    devfront:
        build:
            context: ./devfront
            dockerfile: Dockerfile
        container_name: baron_devfront
        env_file:
            - .env
        environment:
            - APP_ENV=${APP_ENV:-stage}
            - API_PROXY_TARGET=http://baron_backend:3000
        ports:
            - "${DEVFRONT_PORT:-5174}:5173"
        volumes:
            - ./devfront:/app
            - ./common:/common
            - ./locales:/locales
            - /app/node_modules
        networks:
            - baron_net
        healthcheck:
            test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:5173/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
            interval: 10s
            timeout: 5s
            retries: 12
            start_period: 90s

    orgfront:
        build:
            context: ./orgfront
            dockerfile: Dockerfile
        container_name: baron_orgfront
        env_file:
            - .env
        environment:
            - APP_ENV=${APP_ENV:-stage}
            - API_PROXY_TARGET=http://baron_backend:3000
            - USERFRONT_URL=${USERFRONT_URL}
        ports:
            - "${ORGFRONT_PORT:-5175}:5175"
        volumes:
            - ./orgfront:/app
            - ./common:/common
            - ./locales:/locales
            - /app/node_modules
        networks:
            - baron_net
        healthcheck:
            test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:5175/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
            interval: 10s
            timeout: 5s
            retries: 12
            start_period: 90s

    userfront:
        build:
            context: .
            dockerfile: userfront/Dockerfile
        container_name: baron_userfront
        env_file:
            - .env
        environment:
            - BACKEND_URL=${BACKEND_URL:-}
            - USERFRONT_URL=${USERFRONT_URL}
            - APP_ENV=${APP_ENV}
        networks:
            - baron_net
            - ory-net
        depends_on:
            backend:
                condition: service_healthy
        command: >
            /bin/sh -c "mkdir -p /usr/share/nginx/html/assets &&
                        echo \"BACKEND_URL=$${BACKEND_URL}\" >> /usr/share/nginx/html/assets/.env &&
                        echo \"USERFRONT_URL=$${USERFRONT_URL}\" >> /usr/share/nginx/html/assets/.env &&
                        echo \"APP_ENV=$${APP_ENV}\" >> /usr/share/nginx/html/assets/.env &&
                        cp /usr/share/nginx/html/assets/.env /usr/share/nginx/html/.env &&
                        nginx -g 'daemon off;'"
        healthcheck:
            test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
            interval: 10s
            timeout: 5s
            retries: 3
            start_period: 10s

    infra_check:
        image: alpine
        command: ["echo", "Infrastructure assumed running"]
        networks:
            - baron_net

volumes:
    postgres_data:
    clickhouse_data:
    redis_data:
    ory_postgres_data:
    ory_clickhouse_data:
    oathkeeper_logs:

networks:
    baron_net:
        external: true
        name: baron_net
    public_net:
        external: true
        name: public_net
    ory-net:
        external: true
        name: ory-net
    hydranet:
        external: true
        name: hydranet
    kratosnet:
        external: true
        name: kratosnet
