import dotenv from "dotenv";
import { z } from "zod";

import {
  defaultAgentModelOptionsJson,
  getAgentModelIds,
  parseAgentModelOptions,
} from "./chat/modelConfig.js";

// 本地开发可在项目根目录放 .local.env；已存在的系统环境变量优先级更高。
dotenv.config({ path: ".local.env", override: false });

const optionalString = () =>
  z.preprocess(
    (value) => {
      if (typeof value !== "string") {
        return value;
      }
      const normalized = value.trim();
      return normalized.length > 0 ? normalized : undefined;
    },
    z.string().optional(),
  );

// 统一在启动时解析环境变量，避免业务代码里散落字符串默认值。
const envSchema = z
  .object({
    // 运行环境标识，如 development / production。
    NODE_ENV: z.string().default("development"),
    // HTTP 服务监听端口。
    PORT: z.coerce.number().int().positive().default(8787),
    // HTTP 服务监听地址。
    HOST: z.string().default("0.0.0.0"),
    // Pino 日志级别。
    LOG_LEVEL: z.string().default("info"),
    // LLM 工具/技能调用审计日志路径。
    LLM_REQUEST_AUDIT_LOG_PATH: z
      .string()
      .default("./logs/llm-request-audit.log"),
    // 内部工具桥调用本服务时使用的鉴权 token；未显式配置时启动阶段会自动生成。
    AGENT_INTERNAL_TOKEN: optionalString(),
    // Agent 前置认证调用后端 /api/v1/agent/auth/context 的超时时间（毫秒）。
    AGENT_AUTH_TIMEOUT_MS: z.coerce.number().int().positive().default(5000),
    // opencode 运行模式：embedded 会启动本地 CLI 子进程；client 只连接现有 server。
    OPENCODE_MODE: z.enum(["embedded", "client"]).default("embedded"),
    // embedded opencode server 的监听地址。
    OPENCODE_HOSTNAME: z.string().default("127.0.0.1"),
    // embedded opencode server 的监听端口。
    OPENCODE_PORT: z.coerce.number().int().positive().default(4096),
    // opencode SDK 启动或连接运行时时的超时时间（毫秒）。
    OPENCODE_TIMEOUT_MS: z.coerce.number().int().positive().default(5000),
    // 默认使用的 opencode 模型标识。
    OPENCODE_MODEL: z.string().default("deepseek/deepseek-v4-flash"),
    // 聊天 UI 和 /stream 允许选择的 opencode 模型完整配置，JSON 数组。
    OPENCODE_MODEL_OPTIONS: z.string().default(defaultAgentModelOptionsJson),
    // opencode skills 树目录；会在运行时解析为绝对路径，避免工具 cwd 偏移。
    OPENCODE_SKILLS_ROOT_DIR: z.string().default("./.opencode/skills"),
    // client 模式下，目标 opencode server 的基础地址。
    OPENCODE_CLIENT_BASE_URL: z.string().url().optional(),
    // 旧版 client 模式环境变量名，保留兼容，解析时会映射到 OPENCODE_CLIENT_BASE_URL。
    OPENCODE_BASE_URL: z.string().url().optional(),
    // tjwater-cli 可执行文件路径。
    TJWATER_CLI_PATH: z.string().default("./cli/tjwater-cli"),
    // TJWater 后端 API 的基础地址。
    TJWATER_API_BASE_URL: z.string().default("http://127.0.0.1:8000"),
    // 代理调用 TJWater 后端 API 的超时时间（毫秒）。
    TJWATER_API_TIMEOUT_MS: z.coerce.number().int().positive().default(30000),
    // 后端结果在直接内联返回给模型前允许的最大字节数。
    MAX_INLINE_RESULT_BYTES: z.coerce.number().int().positive().default(12000),
    // 生成结果 preview 时最多抽样的条目数。
    MAX_PREVIEW_SAMPLE_ITEMS: z.coerce.number().int().positive().default(3),
    // memory 持久化存储目录。
    MEMORY_STORAGE_DIR: z.string().default("./data/memory"),
    // 持久化文件写入前保留备份版本的目录。
    PERSISTENCE_BACKUP_DIR: z.string().default("./data/backup"),
    // 注入到 prompt 的 memory 快照最大字符数，避免上下文过大。
    MEMORY_MAX_PROMPT_CHARS: z.coerce.number().int().positive().default(1800),
    // session transcript 持久化目录。
    SESSION_TRANSCRIPT_STORAGE_DIR: z.string().default("./data/session-transcripts"),
    // session metadata 持久化目录。
    SESSION_METADATA_STORAGE_DIR: z.string().default("./data/session-metadata"),
    // session UI state 持久化目录。
    SESSION_UI_STATE_STORAGE_DIR: z.string().default("./data/session-ui-states"),
    // 每个会话最多保留多少轮 transcript，超过后裁剪旧记录。
    SESSION_TRANSCRIPT_MAX_TURNS_PER_SESSION: z.coerce
      .number()
      .int()
      .positive()
      .default(120),
    // session_search 工具默认返回的最大命中数。
    SESSION_SEARCH_MAX_RESULTS: z.coerce.number().int().positive().default(8),
    // session_search 查询文本最大长度。
    SESSION_SEARCH_MAX_QUERY_CHARS: z.coerce.number().int().positive().default(240),
    // 当前 session 的 learning 进度状态目录。
    SESSION_LEARNING_STATE_STORAGE_DIR: z.string().default("./data/session-learning-state"),
    // learning audit 日志路径。
    LEARNING_AUDIT_LOG_PATH: z
      .string()
      .default("./logs/learning-audit.log"),
    // learning gate 的最小 turn 冷却间隔；这是运行时节流，不参与内容判断。
    LEARNING_GATE_TURN_COOLDOWN: z.coerce.number().int().positive().default(2),
    // gate 结果被提升为 review 前的最低置信度。
    LEARNING_GATE_MIN_CONFIDENCE: z.coerce.number().min(0).max(1).default(0.65),
    // review prompt 最多携带多少轮最近 transcript。
    LEARNING_REVIEW_MAX_RECENT_TURNS: z.coerce.number().int().positive().default(8),
    // review proposal 的最低置信度阈值。
    LEARNING_MIN_PROPOSAL_CONFIDENCE: z.coerce.number().min(0).max(1).default(0.8),
    // result_ref 持久化存储目录。
    RESULT_REF_STORAGE_DIR: z.string().default("./data/result-refs"),
    // result_ref 保留时长（小时）。
    RESULT_REF_TTL_HOURS: z.coerce.number().int().positive().default(168),
    // 定时清理过期 result_ref 的扫描周期（毫秒）。
    RESULT_REF_CLEANUP_INTERVAL_MS: z.coerce
      .number()
      .int()
      .positive()
      .default(3600000),
  })
  .superRefine((env, ctx) => {
    if (env.OPENCODE_MODE === "client" && !env.OPENCODE_CLIENT_BASE_URL) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ["OPENCODE_CLIENT_BASE_URL"],
        message: "OPENCODE_CLIENT_BASE_URL is required when OPENCODE_MODE=client",
      });
    }
    let modelOptions;
    try {
      modelOptions = parseAgentModelOptions(env.OPENCODE_MODEL_OPTIONS);
    } catch (error) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ["OPENCODE_MODEL_OPTIONS"],
        message: error instanceof Error ? error.message : String(error),
      });
      return;
    }
    const supportedModels = getAgentModelIds(modelOptions);
    if (supportedModels.length === 0) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ["OPENCODE_MODEL_OPTIONS"],
        message: "OPENCODE_MODEL_OPTIONS must include at least one model",
      });
    }
    if (!supportedModels.includes(env.OPENCODE_MODEL)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ["OPENCODE_MODEL"],
        message: "OPENCODE_MODEL must be included in OPENCODE_MODEL_OPTIONS",
      });
    }
  });

export type AppConfig = z.infer<typeof envSchema>;

const normalizedEnv = {
  ...process.env,
  OPENCODE_MODE:
    process.env.OPENCODE_MODE ??
    (process.env.OPENCODE_CLIENT_BASE_URL || process.env.OPENCODE_BASE_URL
      ? "client"
      : "embedded"),
  OPENCODE_CLIENT_BASE_URL:
    process.env.OPENCODE_CLIENT_BASE_URL ?? process.env.OPENCODE_BASE_URL,
};

export const config: AppConfig = envSchema.parse(normalizedEnv);
