import { z } from "zod/v4";
import {
  DpWorkerRuntimeMode,
  DpWorkerUpgradeStatus,
  EffectiveDirectorStatus,
  IanaTimezone,
  LlmProtocol,
  ProxyProtocol,
} from "./domain.js";

/**
 * Request/response contracts for the `/configs/*` REST family — LLM profile
 * and proxy CRUD that the Web UI drives. Kept distinct from
 * `rest-contracts.ts` so the wave-1 UI work doesn't touch the
 * already-stable bootstrap/task schemas.
 *
 * Notes:
 *   - LLM profile responses include `api_key` / `image_api_key` in plaintext
 *     for the admin UI's reveal/copy affordance (internal-use project; see
 *     CLAUDE.md). dp-worker bootstrap reads the same secrets via a separate
 *     schema (`LlmProfileSchema` in rest-contracts.ts).
 *   - Proxy responses include `password` in plaintext for the same reason
 *     (admin UI reveal/copy).
 *   - `base_url` is sanitized server-side per the project convention (strip
 *     trailing `/v1`); the client can send either form.
 */

/* ============================================================================
 * LLM profiles
 * ============================================================================ */

export const LlmProfileBodySchema = z.object({
  name: z.string().min(1).max(120),
  protocol: LlmProtocol,
  base_url: z.string().min(1).max(500),
  api_key: z.string().min(1),
  model: z.string().min(1).max(120),
  image_model: z.string().min(1).max(120).optional(),
  image_api_key: z.string().min(1).optional(),
  image_size: z.string().min(1).max(40).optional(),
});
export type LlmProfileBody = z.infer<typeof LlmProfileBodySchema>;

export const LlmProfilePatchSchema = z
  .object({
    name: z.string().min(1).max(120).optional(),
    protocol: LlmProtocol.optional(),
    base_url: z.string().min(1).max(500).optional(),
    api_key: z.string().min(1).optional(),
    model: z.string().min(1).max(120).optional(),
    image_model: z.string().min(1).max(120).nullable().optional(),
    image_api_key: z.string().min(1).nullable().optional(),
    image_size: z.string().min(1).max(40).nullable().optional(),
  })
  .refine((v) => Object.keys(v).length > 0, {
    message: "patch body must contain at least one field",
  });
export type LlmProfilePatch = z.infer<typeof LlmProfilePatchSchema>;

export const LlmProfileListItemSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  protocol: LlmProtocol,
  base_url: z.string(),
  api_key: z.string(),
  model: z.string(),
  image_model: z.string().optional(),
  image_api_key: z.string().optional(),
  image_size: z.string().optional(),
  in_use_by_worker_count: z.number().int().nonnegative(),
  created_at: z.string().datetime(),
  updated_at: z.string().datetime(),
});
export type LlmProfileListItem = z.infer<typeof LlmProfileListItemSchema>;

export const LlmProfileTestResultSchema = z.object({
  ok: z.boolean(),
  latency_ms: z.number().int().nonnegative(),
  detail: z.string(),
});
export type LlmProfileTestResult = z.infer<
  typeof LlmProfileTestResultSchema
>;

/* ============================================================================
 * Proxies
 * ============================================================================ */

export const ProxyBodySchema = z.object({
  label: z.string().min(1).max(120),
  protocol: ProxyProtocol,
  host: z.string().min(1).max(255),
  port: z.number().int().positive().max(65535),
  username: z.string().min(1).max(255),
  password: z.string().min(1),
  timezone: IanaTimezone,
  note: z.string().max(2000).optional(),
});
export type ProxyBody = z.infer<typeof ProxyBodySchema>;

export const ProxyPatchSchema = z
  .object({
    label: z.string().min(1).max(120).optional(),
    protocol: ProxyProtocol.optional(),
    host: z.string().min(1).max(255).optional(),
    port: z.number().int().positive().max(65535).optional(),
    username: z.string().min(1).max(255).optional(),
    password: z.string().min(1).optional(),
    timezone: IanaTimezone.optional(),
    note: z.string().max(2000).nullable().optional(),
  })
  .refine((v) => Object.keys(v).length > 0, {
    message: "patch body must contain at least one field",
  });
export type ProxyPatch = z.infer<typeof ProxyPatchSchema>;

export const ProxyListItemSchema = z.object({
  id: z.string().uuid(),
  label: z.string(),
  protocol: ProxyProtocol,
  host: z.string(),
  port: z.number().int().positive(),
  username: z.string(),
  password: z.string(),
  timezone: IanaTimezone,
  note: z.string().optional(),
  in_use_by_worker_id: z.string().uuid().optional(),
  in_use_by_worker_name: z.string().optional(),
  bound_worker_id: z.string().uuid().optional(),
  bound_worker_name: z.string().optional(),
  bound_identity_id: z.string().uuid().optional(),
  bound_identity_name: z.string().optional(),
  /** How many identities currently have this proxy bound. */
  bound_identity_count: z.number().int().nonnegative().default(0),
  created_at: z.string().datetime(),
  updated_at: z.string().datetime(),
});
export type ProxyListItem = z.infer<typeof ProxyListItemSchema>;

export const ProxyTestResultSchema = z.object({
  ok: z.boolean(),
  latency_ms: z.number().int().nonnegative(),
  detail: z.string(),
});
export type ProxyTestResult = z.infer<typeof ProxyTestResultSchema>;

/* ============================================================================
 * dp-directors
 * ============================================================================ */

export const DirectorListItemSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  host_label: z.string(),
  api_key: z.string(),
  status: EffectiveDirectorStatus,
  max_concurrent_tasks: z.number().int().nonnegative().default(0),
  last_heartbeat_at: z.string().datetime().optional(),
  version: z.string().optional(),
  dp_worker_version: z.string().optional(),
  dp_worker_latest_version: z.string().optional(),
  dp_worker_runtime_mode: DpWorkerRuntimeMode.optional(),
  dp_worker_upgrade_status: DpWorkerUpgradeStatus.optional(),
  dp_worker_upgrade_message: z.string().optional(),
  dp_worker_upgrade_started_at: z.string().datetime().optional(),
  dp_worker_upgrade_finished_at: z.string().datetime().optional(),
  created_at: z.string().datetime(),
  updated_at: z.string().datetime(),
});
export type DirectorListItem = z.infer<typeof DirectorListItemSchema>;

export const DirectorCreateBodySchema = z.object({
  name: z.string().trim().min(1),
  host_label: z.string().trim().min(1),
  api_key: z.string().trim().min(1).optional(),
  max_concurrent_tasks: z.number().int().nonnegative().default(0),
});
export type DirectorCreateBody = z.infer<typeof DirectorCreateBodySchema>;

export const DirectorPatchSchema = z
  .object({
    name: z.string().trim().min(1).optional(),
    host_label: z.string().trim().min(1).optional(),
    api_key: z.string().trim().min(1).optional(),
    max_concurrent_tasks: z.number().int().nonnegative().optional(),
  })
  .refine((v) => Object.keys(v).length > 0, {
    message: "patch body must contain at least one field",
  });
export type DirectorPatch = z.infer<typeof DirectorPatchSchema>;

/* ============================================================================
 * Dispatch settings
 * ============================================================================ */

export const DispatchSettingsSchema = z.object({
  global_max_concurrent_tasks: z.number().int().nonnegative().default(0),
  updated_at: z.string().datetime(),
});
export type DispatchSettings = z.infer<typeof DispatchSettingsSchema>;

export const DispatchSettingsPatchSchema = z
  .object({
    global_max_concurrent_tasks: z.number().int().nonnegative().optional(),
  })
  .refine((v) => Object.keys(v).length > 0, {
    message: "patch body must contain at least one field",
  });
export type DispatchSettingsPatch = z.infer<
  typeof DispatchSettingsPatchSchema
>;
