import { z } from 'zod';
import { PagingResponseSchema } from './common';

// =============================================================
// Host-level config types(Host.pullConfig / fieldMapping / actionUrls 形状)
//
// 跟 schema.prisma Platform 模型 docstring 一致。
// =============================================================

/// 拉取资源 endpoint(可选,宿主能提供哪个就配哪个)+ 点查接口(users)。
/// key 跟 patient_transactions.subject_type 对齐 + patients 主档 + users 点查。
export const PullEndpointsSchema = z.object({
  patients: z.string().url().optional(),
  consultations: z.string().url().optional(),
  appointments: z.string().url().optional(),
  visit_registrations: z.string().url().optional(),
  visit_receptions: z.string().url().optional(),
  clinical_encounters: z.string().url().optional(),
  emr_documents: z.string().url().optional(),
  charge_orders: z.string().url().optional(),
  payments: z.string().url().optional(),
  refunds: z.string().url().optional(),
  image_studies: z.string().url().optional(),
  /// 点查:POST /users:resolve 解析 userId → name(token 字典覆盖不到时兜底)
  users: z.string().url().optional(),
});
export type PullEndpoints = z.infer<typeof PullEndpointsSchema>;

/// 鉴权(二选一)— 对宿主所有出站调用共用。
export const PullCredentialsSchema = z.object({
  loginUrl: z.string().url().optional(),
  username: z.string().optional(),
  /// Password / secret;存储侧加密,admin endpoint 不会读回
  password: z.string().optional(),
  apiKey: z.string().optional(),
});
export type PullCredentials = z.infer<typeof PullCredentialsSchema>;

/// 金额单位 — 必填无默认,接入时显式声明
export const AmountUnitSchema = z.enum(['fen', 'yuan']);
export type AmountUnit = z.infer<typeof AmountUnitSchema>;

/// 拉取 + 出站调用配置;null = PAC 不主动调用宿主(纯 push 接入或不贡献数据)
export const PullConfigSchema = z.object({
  endpoints: PullEndpointsSchema,
  credentials: PullCredentialsSchema.optional(),
  amountUnit: AmountUnitSchema,
  timezone: z.string().min(1).describe('IANA 时区,如 Asia/Shanghai'),
  intervalMinutes: z.number().int().min(1).max(1440).default(5),
});
export type PullConfig = z.infer<typeof PullConfigSchema>;

/// 动作跳转 URL 模板(VIEW_PATIENT / CREATE_APPOINTMENT 等)。
/// null/缺失 = 前端不渲染该按钮
export const ActionUrlsConfigSchema = z
  .record(z.string(), z.string().nullable())
  .describe('Action template URLs for host UI deep-links');
export type ActionUrlsConfig = z.infer<typeof ActionUrlsConfigSchema>;

// =============================================================
// Host CRUD
// =============================================================

export const CreateHostRequestSchema = z.strictObject({
  /// 平台名 — 英文 slug 风格(friday / 5i5y / dw / demo / mock)。
  /// 子表关联走 id(UUID),name 仅人读用
  name: z
    .string()
    .min(1)
    .max(64)
    .regex(/^[a-z0-9_-]+$/i, 'name must be alphanumeric / dash / underscore'),
  pullConfig: PullConfigSchema.nullable().optional(),
  /// 创建时开启 push 接入 → 服务端会生成 1 个 HMAC 密钥,plaintext 一次返回
  pushEnabled: z.boolean().default(false),
  // 注:host raw → PAC canonical 投影由 AssemblerEngine + 文件级 assembler.yaml 配置驱动,
  // 不再放在 Host 表里(v1 fieldMapping 已删)。
  actionUrls: ActionUrlsConfigSchema.optional(),
});
export type CreateHostRequest = z.infer<typeof CreateHostRequestSchema>;

/// 创建响应:**appSecret / pushSecret plaintext 一次性返回**,宿主必须立即存好;
/// 后续读操作只返回 hashes
export const CreateHostResponseSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  appId: z.string(),
  appSecret: z.string().describe('One-time plaintext app secret'),
  pushSecret: z
    .string()
    .nullable()
    .describe('One-time plaintext HMAC secret if pushEnabled; null otherwise'),
  active: z.boolean(),
});
export type CreateHostResponse = z.infer<typeof CreateHostResponseSchema>;

/// 净化视图(不暴露 secret hashes / 凭证)
export const HostSummarySchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  appId: z.string(),
  pullEnabled: z.boolean(),
  /// push 是否可用(基于 pushSecretHashes 数组是否非空)+ 当前活跃密钥数(>1 = 轮换 grace 期)
  pushEnabled: z.boolean(),
  pushKeyCount: z.number().int().min(0),
  active: z.boolean(),
  createdAt: z.string(),
  updatedAt: z.string(),
});
export type HostSummary = z.infer<typeof HostSummarySchema>;

export const ListHostsResponseSchema = z.object({
  items: z.array(HostSummarySchema),
});
export type ListHostsResponse = z.infer<typeof ListHostsResponseSchema>;

export const UpdateHostRequestSchema = z
  .strictObject({
    pullConfig: PullConfigSchema.nullable().optional(),
    actionUrls: ActionUrlsConfigSchema.optional(),
    active: z.boolean().optional(),
    /// 执行结果回执地址;传空串 / null = 关闭回执
    callbackUrl: z.string().url().nullable().optional(),
  })
  .refine((v) => Object.keys(v).length > 0, 'At least one field is required');
export type UpdateHostRequest = z.infer<typeof UpdateHostRequestSchema>;

export const RotateSecretResponseSchema = z.object({
  appSecret: z.string().describe('New plaintext app secret; save now — cannot be retrieved later'),
});
export type RotateSecretResponse = z.infer<typeof RotateSecretResponseSchema>;

/// Push 密钥**新增**而非替换 —— 数组形式支持平滑轮换。
/// 索引 0 永远是 current,后置为 grace period 内仍接受的旧 key。
export const RotatePushSecretResponseSchema = z.object({
  pushSecret: z.string().describe('New plaintext HMAC secret added to front of array'),
  totalKeys: z.number().int().min(1).describe('Total keys in rotation after this add'),
});
export type RotatePushSecretResponse = z.infer<typeof RotatePushSecretResponseSchema>;

/// 回执密钥新增(语义同 push:数组前插,索引 0 = current 用于签名)
export const RotateCallbackSecretResponseSchema = z.object({
  callbackSecret: z.string().describe('New plaintext callback HMAC secret added to front of array'),
  totalKeys: z.number().int().min(1).describe('Total keys in rotation after this add'),
});
export type RotateCallbackSecretResponse = z.infer<typeof RotateCallbackSecretResponseSchema>;

// =============================================================
// Host detail(给宿主自己的 admin 页用 — 比 Summary 多 pullConfig 内容 + push secret meta)
// =============================================================

/// 凭据 mask 视图:不返回明文,只给前缀+后缀帮 admin 识别
export const MaskedSecretSchema = z.object({
  /// 前 6 + ... + 后 4(共约 10-14 字符);明文从未离开 DB
  masked: z.string(),
  /// 上次轮换时间(若有);新建 host 时和 createdAt 一致
  rotatedAt: z.string().nullable(),
});
export type MaskedSecret = z.infer<typeof MaskedSecretSchema>;

/// Push secret entry — list 渲染用
export const PushSecretEntrySchema = z.object({
  /// 数组索引(0=current)
  index: z.number().int().min(0),
  /// 末 4 字符 + 'idx#' 唯一定位(从 hash 算)
  suffix: z.string().describe('用作删除时的唯一定位 — 不暴露 hash 本身'),
  /// current=数组 0;grace=后续
  role: z.enum(['current', 'grace']),
});
export type PushSecretEntry = z.infer<typeof PushSecretEntrySchema>;

/// 运行状态(给 admin 看健康度,与可编辑配置并列展示)
export const HostStatsSchema = z.object({
  patientCount: z.number().int().min(0),
  transactionCount24h: z.number().int().min(0),
  transactionCount30d: z.number().int().min(0),
  factCount: z.number().int().min(0),
  lastPullAt: z.string().nullable(),
  lastPullStatus: z.string().nullable(),
  lastPullCursor: z.string().nullable(),
  lastPushAt: z.string().nullable(),
  lastPushStatus: z.string().nullable(),
  /// 24h AI 调用 / 成本
  aiInvocations24h: z.number().int().min(0),
  aiCostYuan24h: z.number(),
  /// 24h 失败率(0-1)
  failureRate24h: z.number().min(0).max(1),
  /// 最近 10 条 sync log(给"最近事件"时间轴用)
  recentEvents: z.array(
    z.object({
      kind: z.enum(['pull', 'push', 'reconcile', 'cold_import']),
      status: z.string(),
      startedAt: z.string(),
      transactionsWritten: z.number().int().min(0),
      errorMessage: z.string().nullable(),
    }),
  ),
});
export type HostStats = z.infer<typeof HostStatsSchema>;

/// 完整 detail — 给 host self admin 页拉一次显示全部
export const HostDetailSchema = z.object({
  // 基本(只读)
  id: z.string().uuid(),
  name: z.string(),
  appId: z.string(),
  createdAt: z.string(),
  updatedAt: z.string(),
  active: z.boolean(),

  // 凭据 mask 视图
  appSecret: MaskedSecretSchema,
  pushSecrets: z.array(PushSecretEntrySchema),
  /// 回执密钥列表(结构同 push;索引 0 = current 用于签名)
  callbackSecrets: z.array(PushSecretEntrySchema),

  // Pull 配置(可编辑)
  pullConfig: PullConfigSchema.nullable(),

  // Action URLs(可编辑)
  actionUrls: ActionUrlsConfigSchema,

  /// 执行结果回执地址(可编辑);null = 未启用回执
  callbackUrl: z.string().nullable(),

  // 运行状态
  stats: HostStatsSchema,
});
export type HostDetail = z.infer<typeof HostDetailSchema>;

/// 删除某条 push secret(按 suffix 定位,不暴露 hash)
export const RemovePushSecretRequestSchema = z.object({
  suffix: z.string().min(1).describe('PushSecretEntry.suffix 的值'),
});
export type RemovePushSecretRequest = z.infer<typeof RemovePushSecretRequestSchema>;

// =============================================================
// AI Invocations debug(/admin/ai-invocations — 看 prompt 改后效果 / cost / 失败)
// =============================================================

/// 列表筛选
export const AiInvocationListQuerySchema = z.object({
  callKey: z.string().optional(),
  promptVersion: z.string().optional(),
  modelName: z.string().optional(),
  status: z.string().optional(),         // running / succeeded / failed / cached
  evalMode: z.string().optional(),       // production / golden_set / shadow / manual
  linkedPlanId: z.string().uuid().optional(),
  linkedPatientId: z.string().uuid().optional(),
  page: z.coerce.number().int().min(1).default(1),
  pageSize: z.coerce.number().int().min(1).max(100).default(20),
});
export type AiInvocationListQuery = z.infer<typeof AiInvocationListQuerySchema>;

/// 列表行(轻量,不带 inputSnapshot / output 大字段)
export const AiInvocationListItemSchema = z.object({
  id: z.string().uuid(),
  kind: z.string(),
  callKey: z.string(),
  promptVersion: z.string(),
  modelProvider: z.string(),
  modelName: z.string(),
  status: z.string(),
  cacheHit: z.boolean(),
  evalMode: z.string(),
  workflowRunId: z.string().uuid(),
  parentInvocationId: z.string().uuid().nullable(),
  linkedPatientId: z.string().uuid().nullable(),
  linkedPlanId: z.string().uuid().nullable(),
  promptTokens: z.number().int().min(0),
  completionTokens: z.number().int().min(0),
  totalTokens: z.number().int().min(0),
  /// 输入里命中 vendor cache 的 tokens(DeepSeek prompt_cache_hit_tokens);
  /// hit ratio = cachedInputTokens / promptTokens(同 prompt 反复调时上升)
  cachedInputTokens: z.number().int().min(0),
  /// 输出里 reasoning 部分(thinking 模式);textTokens = completion - reasoning
  reasoningTokens: z.number().int().min(0),
  costYuan: z.number(),
  judgeScore: z.number().nullable(),
  errorMessage: z.string().nullable(),
  startedAt: z.string(),
  endedAt: z.string().nullable(),
  /** 时长 ms(server 算好) */
  durationMs: z.number().int().nullable(),
});
export type AiInvocationListItem = z.infer<typeof AiInvocationListItemSchema>;

export const ListAiInvocationsResponseSchema = z
  .object({ items: z.array(AiInvocationListItemSchema) })
  .extend(PagingResponseSchema.shape);
export type ListAiInvocationsResponse = z.infer<typeof ListAiInvocationsResponseSchema>;

/// 详情(带 inputSnapshot / promptTemplate / output 三个大 JSON / 文本字段)
export const AiInvocationDetailSchema = AiInvocationListItemSchema.extend({
  inputSnapshot: z.unknown(),           // JSON,前端折叠展示
  promptTemplate: z.string().nullable(),
  output: z.unknown().nullable(),        // JSON
  outputText: z.string().nullable(),
  judgeRubric: z.unknown().nullable(),
});
export type AiInvocationDetail = z.infer<typeof AiInvocationDetailSchema>;

/// 总览(给列表页顶部 stat 用)
export const AiInvocationStatsSchema = z.object({
  /** 24h 调用总数(全 kind / 全 callKey 汇总)*/
  count24h: z.number().int().min(0),
  /** 24h 总成本 ¥ */
  cost24h: z.number(),
  /** 24h 失败率(0-1)*/
  failureRate24h: z.number().min(0).max(1),
  /** 按 callKey 分组的 24h 调用数 + 成本(给"热点 prompt"看板) */
  byCallKey: z.array(
    z.object({
      callKey: z.string(),
      count: z.number().int(),
      costYuan: z.number(),
      avgDurationMs: z.number().nullable(),
    }),
  ),
});
export type AiInvocationStats = z.infer<typeof AiInvocationStatsSchema>;

export const RemovePushSecretResponseSchema = z.object({
  totalKeysRemaining: z.number().int().min(0),
});
export type RemovePushSecretResponse = z.infer<typeof RemovePushSecretResponseSchema>;
