import { z } from 'zod';

/**
 * Universal response wrapper.
 *
 * Every PAC API response (except SSE streams + /health) follows this shape:
 *   { code: number, msg: string, data: T | null, details?: unknown }
 *
 * - HTTP status is always 200 except true server crashes (kept as 5xx so
 *   load balancers / k8s probes still work).
 * - `code` follows the 5-digit segmented convention: A-BB-CC
 *     A  : error source / category
 *           0 = success
 *           1 = client error (bad request / auth / permission / not found)
 *           2 = business error (state conflict / rule violation)
 *           3 = third-party / upstream error (host API / AI gateway / DW)
 *           9 = PAC internal error
 *     BB : module
 *           00 common,    01 auth,    02 platform admin
 *           03 patient,   04 persona, 05 plan
 *           06 execution, 07 facts,   08 sync
 *           09 agent,     10 tenant
 *     CC : sequence within module (01, 02, ...)
 */

export const ApiCode = {
  // === 0     Success ============================================
  OK: 0,

  // === 1xxxx Client errors ======================================
  // 100xx — common
  CLIENT_BAD_REQUEST: 10001,
  CLIENT_VALIDATION_FAILED: 10002,
  CLIENT_RATE_LIMITED: 10003,
  CLIENT_NOT_FOUND_GENERIC: 10004,

  // 101xx — auth
  AUTH_INVALID_CREDENTIALS: 10101,
  AUTH_INVALID_CODE: 10102,
  AUTH_INVALID_REFRESH_TOKEN: 10103,
  AUTH_TOKEN_MISSING: 10104,
  AUTH_TOKEN_EXPIRED: 10105,
  AUTH_TOKEN_INVALID: 10106,
  AUTH_PERMISSION_DENIED: 10107,

  // 102xx — platform admin
  HOST_NOT_FOUND: 10201,
  HOST_ALREADY_EXISTS: 10202,

  // 103xx — patient
  PATIENT_NOT_FOUND: 10301,

  // 104xx — persona
  PERSONA_NOT_FOUND: 10401,

  // 105xx — plan
  PLAN_NOT_FOUND: 10501,

  // === 2xxxx Business / state errors ============================
  // 205xx — plan
  PLAN_ALREADY_ASSIGNED: 20501,
  PLAN_ALREADY_COMPLETED: 20502,
  PLAN_BREAKER_TRIPPED: 20503,

  // === 3xxxx Third-party / upstream =============================
  // 308xx — sync engine (host data ingestion)
  SYNC_HOST_UNREACHABLE: 30801,
  SYNC_CONTRACT_DRIFT: 30802,
  SYNC_HOST_AUTH_FAILED: 30803,
  /// 该 platform 的 adapter 没有实现 pullPatient(单患者即时 pull)能力
  SYNC_PATIENT_PULL_NOT_SUPPORTED: 30804,

  // 309xx — agent / AI gateway
  AGENT_GATEWAY_TIMEOUT: 30901,
  AGENT_GATEWAY_ERROR: 30902,

  // === 9xxxx PAC internal =======================================
  INTERNAL_ERROR: 90000,
  INTERNAL_DB_ERROR: 90001,
} as const;
export type ApiCode = (typeof ApiCode)[keyof typeof ApiCode];

/**
 * Default human-readable messages keyed by ApiCode. Services that throw
 * BizError can pass an explicit message; otherwise the filter uses this
 * lookup as a fallback.
 */
export const API_CODE_MESSAGES: Record<number, string> = {
  [ApiCode.OK]: 'ok',
  [ApiCode.CLIENT_BAD_REQUEST]: '请求格式错误',
  [ApiCode.CLIENT_VALIDATION_FAILED]: '请求字段校验失败',
  [ApiCode.CLIENT_RATE_LIMITED]: '请求过于频繁',
  [ApiCode.SYNC_PATIENT_PULL_NOT_SUPPORTED]: '该平台不支持单患者即时刷新',
  [ApiCode.CLIENT_NOT_FOUND_GENERIC]: '资源不存在',
  [ApiCode.AUTH_INVALID_CREDENTIALS]: 'appId 或 appSecret 不正确',
  [ApiCode.AUTH_INVALID_CODE]: '一次性 code 无效或已过期',
  [ApiCode.AUTH_INVALID_REFRESH_TOKEN]: 'refresh token 无效',
  [ApiCode.AUTH_TOKEN_MISSING]: '未携带 access token',
  [ApiCode.AUTH_TOKEN_EXPIRED]: 'access token 已过期',
  [ApiCode.AUTH_TOKEN_INVALID]: 'access token 解析失败',
  [ApiCode.AUTH_PERMISSION_DENIED]: '权限不足',
  [ApiCode.HOST_NOT_FOUND]: '宿主不存在',
  [ApiCode.HOST_ALREADY_EXISTS]: '宿主已存在',
  [ApiCode.PATIENT_NOT_FOUND]: '患者不存在',
  [ApiCode.PERSONA_NOT_FOUND]: '画像不存在',
  [ApiCode.PLAN_NOT_FOUND]: '计划不存在',
  [ApiCode.PLAN_ALREADY_ASSIGNED]: '计划已被分配',
  [ApiCode.PLAN_ALREADY_COMPLETED]: '计划已完成,不能再修改',
  [ApiCode.PLAN_BREAKER_TRIPPED]: '计划联系次数超限,已熔断',
  [ApiCode.SYNC_HOST_UNREACHABLE]: '宿主接口不可达',
  [ApiCode.SYNC_CONTRACT_DRIFT]: '宿主字段映射漂移',
  [ApiCode.SYNC_HOST_AUTH_FAILED]: '宿主登录失败 / 凭据失效',
  [ApiCode.AGENT_GATEWAY_TIMEOUT]: 'AI 网关响应超时',
  [ApiCode.AGENT_GATEWAY_ERROR]: 'AI 网关返回错误',
  [ApiCode.INTERNAL_ERROR]: '服务内部错误',
  [ApiCode.INTERNAL_DB_ERROR]: '数据库错误',
};

export function describeApiCode(code: number): string {
  return API_CODE_MESSAGES[code] ?? `Unknown error (${code})`;
}

/// Wrap any inner schema into the standard API response envelope.
export const WrapSchema = <T extends z.ZodType>(inner: T) =>
  z.object({
    code: z.number().int(),
    msg: z.string(),
    data: inner.nullable(),
    details: z.unknown().optional(),
  });

/// Generic helper type for a wrapped response.
export type ApiResponse<T> = {
  code: number;
  msg: string;
  data: T | null;
  details?: unknown;
};

/// The wrapped error shape — used in OpenAPI for error responses.
export const ApiErrorEnvelopeSchema = z.object({
  code: z.number().int(),
  msg: z.string(),
  data: z.null(),
  details: z.unknown().optional(),
});
export type ApiErrorEnvelope = z.infer<typeof ApiErrorEnvelopeSchema>;
