import { z } from 'zod';
import { UserRoleSchema } from '../enums';

// =============================================================
// Token Exchange  (POST /pac/v1/auth/token)
// =============================================================

/// 可选名字典 — host 换票时一并提供 id → 展示名映射,PAC 不持有主数据。
/// clinics / users 都是 host 侧 id → 显示名(中文)。
///
/// ⚠️ 跟 clinicIds(权限)解耦:dictionary 只管"怎么显示名字",可包含用户
/// 权限范围之外的诊所(如 plan.targetClinicId 落在用户没权限的诊所 — 概率低但存在)。
/// 缺失某 id 时前端回退显示原始 id,不报错。
export const TokenDictionarySchema = z
  .object({
    clinics: z.record(z.string(), z.string()).optional(),
    users: z.record(z.string(), z.string()).optional(),
  })
  .describe('Optional id→displayName dictionary (clinics / users), host-provided');
export type TokenDictionary = z.infer<typeof TokenDictionarySchema>;

// `.strict()` on public-facing input: unknown keys are rejected with a clear
// error rather than silently dropped. Catches host-side typos (`userid` →
// `userId`) before they cause confusing downstream behavior.
export const TokenExchangeUserSchema = z
  .strictObject({
    userId: z.string().min(1).describe('Host-side user id'),
    tenantId: z
      .string()
      .min(1)
      .describe("Host-side tenant id (the host's own customer / org id)"),
    role: UserRoleSchema.describe(
      'PAC role granted to this user. PAC ships three: staff (执行客服) / leader (主管) / admin (后台管理员)',
    ),
    orgScope: z
      .array(z.string())
      .describe(
        '数据权限(唯一入口):用户可达的 org 节点 id,任意层级 —— 集团 / 区域(可多级)/ 品牌 / 诊所。' +
          'PAC 按 host org 树 expandOrgScope 展开成内部过滤维(sourceUnits + clinicIds)。' +
          '空数组 = 不限(集团级 / 超管)。诊所级即传诊所 id 列表;没有品牌概念的 host 同理只传诊所 id。',
      ),
    dictionary: TokenDictionarySchema.optional().describe(
      'Optional id→name dictionary for UI display (clinics / users)',
    ),
  })
  .describe('User context for the token being signed');

export const TokenExchangeRequestSchema = z.strictObject({
  appId: z.string().min(1).describe('App id assigned to the host platform'),
  appSecret: z
    .string()
    .min(1)
    .describe('App secret paired with appId. Held server-side only.'),
  user: TokenExchangeUserSchema,
});
export type TokenExchangeRequest = z.infer<typeof TokenExchangeRequestSchema>;

export const TokenExchangeResponseSchema = z.object({
  accessToken: z
    .string()
    .describe('Bearer JWT for subsequent API calls (default 2h TTL)'),
  refreshToken: z
    .string()
    .describe('Refresh JWT — exchange before access token expires'),
  code: z
    .string()
    .describe(
      'One-time exchange code. Pass to the iframe URL; consumed by /auth/exchange-code (default 60s TTL).',
    ),
  expiresIn: z.number().int().describe('Access token lifetime in seconds'),
  codeExpiresIn: z.number().int().describe('One-time code lifetime in seconds'),
});
export type TokenExchangeResponse = z.infer<typeof TokenExchangeResponseSchema>;

// =============================================================
// Exchange Code  (POST /pac/v1/auth/exchange-code)
// =============================================================

export const ExchangeCodeRequestSchema = z.strictObject({
  code: z.string().min(1),
});
export type ExchangeCodeRequest = z.infer<typeof ExchangeCodeRequestSchema>;

export const ExchangeCodeResponseSchema = z.object({
  accessToken: z.string(),
  refreshToken: z.string(),
  expiresIn: z.number().int(),
});
export type ExchangeCodeResponse = z.infer<typeof ExchangeCodeResponseSchema>;

// =============================================================
// Refresh  (POST /pac/v1/auth/refresh)
// =============================================================

export const RefreshTokenRequestSchema = z.strictObject({
  refreshToken: z.string().min(1),
});
export type RefreshTokenRequest = z.infer<typeof RefreshTokenRequestSchema>;

export const RefreshTokenResponseSchema = z.object({
  accessToken: z.string(),
  refreshToken: z.string(),
  expiresIn: z.number().int(),
});
export type RefreshTokenResponse = z.infer<typeof RefreshTokenResponseSchema>;

// =============================================================
// Mock Login  (POST /pac/v1/auth/mock-login) ⭐ 开发 / 试部署用
// =============================================================
//
// 用途:试部署 / 内部演示场景 — 用户访问 /plans 无 token 时,前端弹"快速登录"对话框,
// 后端按 { tenant, role } 预制 user payload 直接产 JWT(跳过 host SSO)
//
// 安全:env 门控 — 只在 NODE_ENV != 'production' 或 PAC_ENABLE_MOCK_LOGIN=true 时启用,
// 生产 host 真接入后置 false / 删该 endpoint
export const MockLoginRequestSchema = z.strictObject({
  /// 租户 slug — 'ruier'(瑞尔) / 'ruitai'(瑞泰) → jvs-dw;'friday' → FRIDAY 市场(集团级)
  tenant: z.enum(['ruier', 'ruitai', 'friday']),
  /// PAC 角色 — staff / leader / admin
  role: UserRoleSchema,
  /// 可选:登录为具体客服(externalId,来自 GET /auth/mock-users)。
  /// 给了则用真实客服身份(sub=externalId / 真实姓名 / 该客服所属诊所);不给走通用预制身份。
  userExternalId: z.string().optional(),
  /// 数据范围层级(org-scope):'group'=集团(看全部品牌全部诊所,sourceUnits=[] 不限)。
  /// 不给:有 userExternalId=诊所级,否则=品牌级(整个 tenant 品牌)。
  orgLevel: z.enum(['group']).optional(),
  /// 直接指定 org 节点 id(品牌 GUID / 诊所 id)作数据范围 —— 多品牌宿主(FRIDAY)用:
  /// 它没有客服花名册,身份是造的,但**品牌/诊所必须是真实摄入数据**(见 GET /auth/mock-orgs)。
  /// 给了 orgId 时 orgScope=[orgId];与 orgLevel 互斥(orgLevel=group 优先)。
  orgId: z.string().min(1).optional(),
});
export type MockLoginRequest = z.infer<typeof MockLoginRequestSchema>;

/// 跟 ExchangeCodeResponse 同形(前端直接 setTokens 用)
export const MockLoginResponseSchema = ExchangeCodeResponseSchema;
export type MockLoginResponse = z.infer<typeof MockLoginResponseSchema>;

// =============================================================
// Mock Users  (GET /pac/v1/auth/mock-users) ⭐ 开发 / 试部署用
// =============================================================
//
// 给"快速登录"对话框的客服花名册(派生自 data/<host>/users.json,回访表口径 · 近 12 月在岗)。
// 选一个客服 → POST /auth/mock-login { tenant, role, userExternalId } 以其真实身份登录。
export const MockUserSchema = z.object({
  externalId: z.string(),
  name: z.string(),
  tenant: z.enum(['ruier', 'ruitai']),
  lastActiveAt: z.string(),
  /// 近 12 月回访量(各诊所合计,排序/展示用)
  contactCount: z.number().int(),
  /// 所属诊所(带中文名,展示用)
  clinics: z.array(z.object({ clinicId: z.string(), name: z.string() })),
});
export type MockUser = z.infer<typeof MockUserSchema>;

export const MockUsersResponseSchema = z.object({ users: z.array(MockUserSchema) });
export type MockUsersResponse = z.infer<typeof MockUsersResponseSchema>;

// =============================================================
// Mock Orgs  (GET /pac/v1/auth/mock-orgs) ⭐ 开发 / 试部署用
// =============================================================
//
// 给"快速登录"对话框的**真实**组织结构(品牌 → 诊所),从摄入数据派生的 org 树 +
// host 名字典(sourceUnitNames / clinicNames)。多品牌宿主(FRIDAY)没有客服花名册,
// 身份可造,但数据范围必须落在真实品牌/诊所上 —— 本端点提供可选项。
export const MockOrgClinicSchema = z.object({
  clinicId: z.string(),
  name: z.string(),
});
export const MockOrgBrandSchema = z.object({
  /// 品牌节点 id(= patients.source_unit;多品牌宿主是 GUID)
  brandId: z.string(),
  name: z.string(),
  clinics: z.array(MockOrgClinicSchema),
});
export type MockOrgBrand = z.infer<typeof MockOrgBrandSchema>;
export const MockOrgsResponseSchema = z.object({
  /// 集团(租户)节点 id — 集团级登录传它
  groupId: z.string(),
  brands: z.array(MockOrgBrandSchema),
});
export type MockOrgsResponse = z.infer<typeof MockOrgsResponseSchema>;

// =============================================================
// JWT payload — server-only; never sent to host
// =============================================================

export const AccessTokenPayloadSchema = z.object({
  sub: z.string(),
  hostId: z.string(),
  tenantId: z.string(),
  /// 数据权限(与对外契约同词汇):用户可达的 org 节点 id,任意层级。
  /// PAC 在每个请求(tenant-scope 拦截器 / MCP 鉴权)按 host org 树展开成内部过滤维
  /// (sourceUnits + clinicIds)。空数组 = 不限(集团级)。
  /// 故 JWT 解出来跟 host 发进来的 org_scope 一致 —— 不泄漏 PAC 内部 schema 词汇。
  orgScope: z.array(z.string()),
  role: UserRoleSchema,
  permissions: z.array(z.string()),
  /// 可选名字典(clinics/users id→名),host 换票时带,前端 UI 用于把 UUID 显示成名字。
  /// 体积小(几条诊所);随 JWT 走,F5/refresh 自动带,前端 decodeJwt 即可取。
  dictionary: TokenDictionarySchema.optional(),
  iat: z.number().int(),
  exp: z.number().int(),
});
export type AccessTokenPayload = z.infer<typeof AccessTokenPayloadSchema>;

// =============================================================
// Session view  (GET /pac/v1/auth/session)
// =============================================================
//
// 前端的「会话视图」—— JWT 只装通用 orgScope(设计 B),前端展不开,故由后端在本端点
// 把 orgScope 展开成内部过滤维(clinicIds/sourceUnits)一并返回。
// 角色/权限/字典前端仍可即时 decodeJwt 拿到;clinicIds/sourceUnits 走本端点补齐。
export const SessionResponseSchema = z.object({
  sub: z.string(),
  hostId: z.string(),
  tenantId: z.string(),
  role: UserRoleSchema,
  permissions: z.array(z.string()),
  /// 通用数据权限(= JWT.orgScope,透传,便于前端展示/调试)
  orgScope: z.array(z.string()),
  /// 展开后的内部过滤维(前端诊所筛选用)
  clinicIds: z.array(z.string()),
  sourceUnits: z.array(z.string()),
  dictionary: TokenDictionarySchema.optional(),
  /// 宿主动作跳转模板(actionUrls)—— 前端"新建预约"等按钮据此打开宿主页;缺失=不渲染。
  actionUrls: z.record(z.string(), z.string().nullable()).optional(),
});
export type SessionResponse = z.infer<typeof SessionResponseSchema>;
