# ============================================
# 阶段 1: 基础镜像
# ============================================
FROM node:18-alpine AS base

# 使用阿里云镜像源加速（所有阶段统一配置）
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

# ============================================
# 阶段 2: 依赖安装 (deps)
# ============================================
FROM base AS deps

# 安装构建时依赖（使用虚拟包，便于后续清理）
# 注意：cairo/pango 主要用于 canvas（可选依赖），sharp 不需要这些
# 如果 canvas 安装失败，应用仍可使用 ag-psd 的 fallback 方法
RUN apk add --no-cache --virtual .build-deps \
    libc6-compat \
    openssl3 \
    openssl-dev \
    # Canvas 相关依赖（可选，用于 ag-psd 的 canvas 支持）
    cairo \
    cairo-dev \
    pango \
    pango-dev \
    jpeg-dev \
    giflib-dev \
    librsvg \
    pixman-dev \
    # 编译工具（用于编译原生模块）
    python3 \
    make \
    g++ \
    pkgconfig

WORKDIR /app

# 复制依赖文件（放在前面以利用 Docker 缓存）
COPY package.json package-lock.json* ./

# 配置 npm（仅在构建阶段，使用国内镜像源加速）
# 注意：这些配置不会进入最终镜像
# sharp 的二进制文件通过环境变量配置，而不是 npm config
RUN npm config set registry https://registry.npmmirror.com/

# 安装所有依赖（包括 devDependencies，构建时需要）
# 使用缓存挂载加速 npm install（需要 BuildKit: DOCKER_BUILDKIT=1）
# 注意：canvas 是可选依赖，如果安装失败，npm ci 不会失败，但会警告
# 如果 npm ci 失败（通常是 package-lock.json 问题），构建会停止
RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline --no-audit

# 清理构建依赖和 npm 配置（保留运行时所需的库）
RUN apk del .build-deps && \
    # 清理 npm 配置（不在运行镜像中保留）
    npm config delete registry && \
    # 只保留运行时库
    apk add --no-cache \
    libc6-compat \
    ca-certificates \
    openssl3

# ============================================
# 阶段 3: 构建应用 (builder)
# ============================================
FROM base AS builder

ARG DATABASE_URL=""
ARG NEXTAUTH_URL=""
ARG APP_PUBLIC_URL=""

ARG PRISMA_ENGINE_CACHE_FILE_NAME="libquery_engine-linux-musl-openssl-3.0.x.so.node"
ARG PRISMA_SCHEMA_ENGINE_CACHE_FILE_NAME="schema-engine-linux-musl-openssl-3.0.x"

ENV PRISMA_ENGINE_CACHE_FILE_NAME=${PRISMA_ENGINE_CACHE_FILE_NAME}
ENV PRISMA_SCHEMA_ENGINE_CACHE_FILE_NAME=${PRISMA_SCHEMA_ENGINE_CACHE_FILE_NAME}

ENV DATABASE_URL=${DATABASE_URL}
ENV NEXTAUTH_URL=${NEXTAUTH_URL}
ENV APP_PUBLIC_URL=${APP_PUBLIC_URL}
ENV NEXT_TELEMETRY_DISABLED=1

WORKDIR /app

# 安装 OpenSSL 3（让 Prisma 能够检测到，消除警告）
RUN apk add --no-cache openssl3

# 设置构建环境变量
ENV NODE_ENV=production
# Prisma 官方偶尔在下载校验或引擎文件时返回 5xx，这里允许缺失校验文件
ENV PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1

# 从 deps 阶段复制 node_modules（包含所有依赖）
COPY --from=deps /app/node_modules ./node_modules

# 复制应用源代码（放在后面，因为源代码变化更频繁）
# 使用 .dockerignore 排除不需要的文件
COPY . .

# 配置 Prisma 引擎缓存路径（可离线使用）
ENV PRISMA_ENGINE_CACHE_PATH=/app/prisma/engine-cache/${PRISMA_ENGINE_CACHE_FILE_NAME}
ENV PRISMA_SCHEMA_ENGINE_CACHE_PATH=/app/prisma/engine-cache/${PRISMA_SCHEMA_ENGINE_CACHE_FILE_NAME}

# 将缓存的引擎文件复制到 Prisma CLI 缓存目录，避免构建时重新下载
RUN set -ex && \
    if [ -f "${PRISMA_ENGINE_CACHE_PATH}" ]; then \
        echo "检测到本地 Prisma 查询引擎缓存，路径：${PRISMA_ENGINE_CACHE_PATH}" && \
        PRISMA_ENGINE_VERSION=$(node -p "require('@prisma/engines-version/package.json').prisma.enginesVersion") && \
        TARGET_DIR="/root/.cache/prisma/master/${PRISMA_ENGINE_VERSION}/linux-musl-openssl-3.0.x" && \
        mkdir -p "${TARGET_DIR}" && \
        cp "${PRISMA_ENGINE_CACHE_PATH}" "${TARGET_DIR}/libquery_engine.so.node" && \
        gzip -c "${PRISMA_ENGINE_CACHE_PATH}" > "${TARGET_DIR}/libquery_engine.so.node.gz" && \
        sha256sum "${TARGET_DIR}/libquery_engine.so.node.gz" | awk '{print $1}' > "${TARGET_DIR}/libquery_engine.so.node.gz.sha256"; \
    else \
        echo "未找到本地 Prisma 查询引擎缓存（${PRISMA_ENGINE_CACHE_PATH}），将尝试在线下载"; \
    fi && \
    if [ -f "${PRISMA_SCHEMA_ENGINE_CACHE_PATH}" ]; then \
        echo "检测到本地 Prisma schema 引擎缓存，路径：${PRISMA_SCHEMA_ENGINE_CACHE_PATH}" && \
        PRISMA_ENGINE_VERSION=$(node -p "require('@prisma/engines-version/package.json').prisma.enginesVersion") && \
        TARGET_DIR="/root/.cache/prisma/master/${PRISMA_ENGINE_VERSION}/linux-musl-openssl-3.0.x" && \
        mkdir -p "${TARGET_DIR}" && \
        cp "${PRISMA_SCHEMA_ENGINE_CACHE_PATH}" "${TARGET_DIR}/schema-engine" && \
        chmod +x "${TARGET_DIR}/schema-engine" && \
        gzip -c "${PRISMA_SCHEMA_ENGINE_CACHE_PATH}" > "${TARGET_DIR}/schema-engine.gz" && \
        sha256sum "${TARGET_DIR}/schema-engine.gz" | awk '{print $1}' > "${TARGET_DIR}/schema-engine.gz.sha256"; \
    else \
        echo "未找到本地 Prisma schema 引擎缓存（${PRISMA_SCHEMA_ENGINE_CACHE_PATH}），将尝试在线下载"; \
    fi

# 生成 Prisma Client（需要完整的 node_modules）
# 默认使用官方源，如遇 5xx 再自动切换到 npm 镜像源重试
RUN set -ex && \
    if [ -f "${PRISMA_ENGINE_CACHE_PATH}" ]; then \
        export PRISMA_QUERY_ENGINE_LIBRARY="${PRISMA_ENGINE_CACHE_PATH}" && \
        export PRISMA_CLIENT_ENGINE_TYPE=library && \
        export PRISMA_CLI_QUERY_ENGINE_TYPE=library && \
        echo "使用本地 Prisma 查询引擎缓存优先生成 Client"; \
    else \
        echo "本地 Prisma 引擎缓存不可用，将使用在线下载"; \
        unset PRISMA_QUERY_ENGINE_LIBRARY PRISMA_CLIENT_ENGINE_TYPE PRISMA_CLI_QUERY_ENGINE_TYPE; \
    fi && \
    if [ -f "${PRISMA_SCHEMA_ENGINE_CACHE_PATH}" ]; then \
        export PRISMA_SCHEMA_ENGINE_BINARY="${PRISMA_SCHEMA_ENGINE_CACHE_PATH}" && \
        echo "使用本地 Prisma schema 引擎缓存"; \
    else \
        echo "本地 Prisma schema 引擎缓存不可用，将使用在线下载"; \
        unset PRISMA_SCHEMA_ENGINE_BINARY; \
    fi && \
    if npx prisma generate; then \
        exit 0; \
    fi && \
    echo "Prisma 官方源下载失败，尝试使用镜像源重试..." && \
    PRISMA_ENGINES_MIRROR="https://registry.npmmirror.com/-/binary/prisma/" npx prisma generate

# 验证关键依赖是否已安装
RUN if [ ! -d "node_modules/.prisma" ]; then \
        echo "错误: Prisma Client 未生成" >&2 && exit 1; \
    fi

    # 构建 Next.js 应用（standalone 模式）
    # 允许构建时出现预渲染错误（这些页面会在运行时动态渲染）
    RUN set -ex && \
        if [ -f "${PRISMA_ENGINE_CACHE_PATH}" ]; then \
            export PRISMA_QUERY_ENGINE_LIBRARY="${PRISMA_ENGINE_CACHE_PATH}" && \
            export PRISMA_CLIENT_ENGINE_TYPE=library && \
            export PRISMA_CLI_QUERY_ENGINE_TYPE=library; \
        else \
            unset PRISMA_QUERY_ENGINE_LIBRARY PRISMA_CLIENT_ENGINE_TYPE PRISMA_CLI_QUERY_ENGINE_TYPE; \
        fi && \
        if [ -f "${PRISMA_SCHEMA_ENGINE_CACHE_PATH}" ]; then \
            export PRISMA_SCHEMA_ENGINE_BINARY="${PRISMA_SCHEMA_ENGINE_CACHE_PATH}"; \
        else \
            unset PRISMA_SCHEMA_ENGINE_BINARY; \
        fi && \
        npm run build || (echo "构建完成，但部分页面预渲染失败（这是预期的，这些页面将在运行时动态渲染）" && true)

# 验证构建产物（允许 ESLint 警告，但不允许致命错误）
RUN if [ ! -f ".next/standalone/server.js" ]; then \
        echo "错误: standalone 输出未生成，请检查 next.config.js 中的 output: 'standalone'" >&2 && \
        echo "提示: 如果构建过程中有 React Hooks 或其他编译错误，请先修复后再构建" >&2 && \
        exit 1; \
    fi

# 为可选模块创建占位符（确保 COPY 不会失败）
# 如果 canvas 未安装，创建一个空占位符目录
RUN if [ ! -d "node_modules/canvas" ]; then \
        echo "Canvas 未安装，创建占位符目录" && \
        mkdir -p node_modules/canvas && \
        echo '{"name":"canvas-placeholder"}' > node_modules/canvas/package.json; \
    fi

# ============================================
# 阶段 4: 生产运行 (runner)
# ============================================
FROM base AS runner

WORKDIR /app

# 设置生产环境变量
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# 只安装运行时必需的库（最小化镜像大小）
# sharp 只需要基本的系统库，不需要 cairo/pango
# 但如果 canvas 被安装（可选依赖），则需要这些库
RUN apk add --no-cache \
    libc6-compat \
    ca-certificates \
    openssl3 \
    # Canvas 运行时库（如果 canvas 被安装则需要，否则可忽略）
    # 注意：如果 canvas 未安装，这些库不会造成问题，只是占用一点空间
    cairo \
    pango \
    jpeg \
    giflib \
    librsvg \
    pixman \
    # 字体支持（canvas 渲染中文时需要）
    fontconfig \
    ttf-dejavu \
    # FFmpeg（视频转码功能，可选）
    # 如果不需要视频转码功能，可以注释掉以下两行以减小镜像大小（约节省 30-45MB）
    ffmpeg \
    ffmpeg-libs

# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nextjs

# 从 builder 阶段复制构建产物
# standalone 输出包含所有必需的依赖和 server.js
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# 复制必要的额外文件
# 1. public 目录（静态资源）
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

# 2. 额外静态资源（如默认 Logo）
COPY --from=builder --chown=nextjs:nodejs /app/logo ./public/logo

# 3. Prisma schema 和迁移文件（运行时可能需要）
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma

# 4. lib 目录（包含 configLoader 等运行时模块）
COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib

# 5. 补充关键模块（standalone 输出可能未完整包含）
# 复制 Prisma Client（必需）
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma
# 复制 Prisma 包，确保运行时存在 @prisma/client 及其生成器
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@prisma ./node_modules/@prisma
# 复制 Prisma CLI（用于运行时执行 migrate deploy）
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/prisma ./node_modules/prisma
# 复制 npm 可执行文件，确保 npx prisma 可用
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.bin ./node_modules/.bin

# 复制 Canvas 模块（可选依赖）
# 如果 canvas 未安装，builder 阶段已创建占位符，所以这个 COPY 不会失败
# 在运行时，如果 canvas 是占位符，应用会使用 ag-psd 的 fallback 方法
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/canvas ./node_modules/canvas

# 复制 ag-psd 模块
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/ag-psd ./node_modules/ag-psd

# 清理占位符（如果 canvas 是占位符，删除它）
RUN if [ -f "./node_modules/canvas/package.json" ] && grep -q "canvas-placeholder" ./node_modules/canvas/package.json 2>/dev/null; then \
        echo "Canvas 未安装，清理占位符，使用 ag-psd fallback" && \
        rm -rf ./node_modules/canvas; \
    fi

# 创建上传目录并设置权限
RUN mkdir -p /app/uploads && \
    chown -R nextjs:nodejs /app/uploads && \
    chmod -R 755 /app/uploads

# 切换到非 root 用户
USER nextjs

EXPOSE 3000

# 使用 standalone 输出的 server.js
CMD ["node", "server.js"]

