import { z } from 'zod';

const telegramSchema = z.object({
  bot_token: z.string().min(1, 'Bot token is required'),
  allowed_chat_ids: z.array(z.number()).min(1, 'At least one chat ID required'),
  require_mention: z.boolean().default(true),
});

const tailscaleSchema = z.object({
  /** Enable Tailscale Serve to expose gateway on tailnet. */
  serve: z.boolean().default(false),
  /** Custom hostname for Tailscale Serve. Defaults to machine hostname. */
  hostname: z.string().optional(),
  /** Tailscale Serve HTTPS port. */
  port: z.number().default(443),
}).default({});

const pairingSchema = z.object({
  /** Enable DM pairing codes for unknown senders. */
  enabled: z.boolean().default(false),
  /** Pairing code time-to-live duration (e.g. '5m', '1h'). */
  code_ttl: z.string().default('5m'),
  /** Length of generated pairing codes. */
  code_length: z.number().default(6),
}).default({});

const serverSchema = z.object({
  tailscale: tailscaleSchema,
  localhost: z.boolean().default(true),
  port: z.number().default(18800),
  /** Static bearer token for gateway auth. If set, all connections must provide it. */
  token: z.string().optional(),
  /** Trust Tailscale-User-Login header for identity. */
  tailscale_identity: z.boolean().default(false),
  /** Apply token auth to HTTP requests too (not just WebSocket). Default: true when token is set. */
  auth_http: z.boolean().default(true),
  /** Single-client gateway lock. When true, only one WebSocket client can be connected at a time. */
  lock: z.boolean().default(false),
});

/** All supported model provider identifiers. Used by the config schema and TUI autocompletion. */
export const MODEL_PROVIDERS = ['anthropic', 'openai', 'gemini', 'ollama', 'llamacpp', 'openrouter', 'vercel', 'bedrock', 'github', 'zhipuai', 'xai', 'synthetic'] as const;

export type ModelProvider = (typeof MODEL_PROVIDERS)[number];

const modelConfigBaseSchema = z.object({
  provider: z.enum(MODEL_PROVIDERS),
  model: z.string(),
  endpoint: z.string().optional(),
  api_key: z.string().optional(),
  auth_token: z.string().optional(),
  /** Credential selection strategy for this tier (provider-specific). */
  auth_mode: z.enum(['auto', 'api_key', 'oauth']).optional(),
  /** Use OAuth credential flow (provider-specific). */
  use_oauth: z.boolean().optional(),
  for: z.array(z.string()).optional(),
  num_gpu: z.number().optional(),
  context_window: z.number().optional(),
  supports_audio: z.boolean().optional(),
});

const modelConfigSchema = modelConfigBaseSchema.extend({
  fallback: modelConfigBaseSchema.optional(),
});

const thinkingSchema = z.object({
  anthropic: z.object({
    budgetTokens: z.number().default(4096),
  }).default({}),
  openai: z.object({
    reasoningEffort: z.enum(['low', 'medium', 'high']).default('medium'),
  }).default({}),
  gemini: z.object({
    budgetTokens: z.number().default(4096),
  }).default({}),
}).default({});

const modelsSchema = z.object({
  local: modelConfigSchema.optional(),
  fast: modelConfigSchema.optional(),
  default: modelConfigSchema,
  complex: modelConfigSchema.optional(),
  fallback_chain: z.array(z.string()).default(['anthropic']),
  local_providers: z.record(z.string(), modelConfigSchema).optional(),
  thinking: thinkingSchema,
});

const backendsSchema = z.object({
  claude_code: z.object({
    enabled: z.boolean().default(false),
    path: z.string().optional(),
  }).default({ enabled: false }),
  opencode: z.object({
    enabled: z.boolean().default(false),
    path: z.string().optional(),
  }).default({ enabled: false }),
  native: z.object({
    enabled: z.boolean().default(true),
  }).default({ enabled: true }),
}).default({});

const hooksSchema = z.object({
  confirm: z.array(z.string()).default([]),
  log: z.array(z.string()).default([]),
  silent: z.array(z.string()).default([]),
}).default({});

const skillsLoadSchema = z.object({
  /** Enable filesystem watcher for automatic skill reload detection. */
  watch: z.boolean().default(false),
  /** Debounce window for batched watcher events. */
  watch_debounce_ms: z.number().min(10).max(10_000).default(250),
}).default({});

const skillsShellRunnerGovernanceSchema = z.object({
  /** Responsible owner for shell-runner allowlist decisions. */
  owner: z.string().min(1).optional(),
  /** Review cadence for allowlist + rollout status checks. */
  review_cadence_days: z.number().min(1).max(90).default(7),
  /** Minimum success rate required before broader rollout. */
  promotion_min_success_rate: z.number().min(0).max(1).default(0.9),
}).default({});

const skillsSchema = z.object({
  /** Directory for user-created workspace skills. */
  workspace_dir: z.string().optional(),
  /** Directory for managed (installed) skills. Defaults to ~/.flynn/workspace/skills. */
  managed_dir: z.string().optional(),
  /** Directory for bundled skills shipped with Flynn. */
  bundled_dir: z.string().optional(),
  /** Global policy gate for installer command execution. */
  installation_execution: z.enum(['disabled', 'enabled']).default('disabled'),
  /** Allow use of the shell runner for installer commands. */
  allow_shell_runner: z.boolean().default(false),
  /** Allowlist patterns for shell runner commands (supports '*' wildcard). */
  shell_runner_allowlist: z.array(z.string()).default([]),
  /** Governance controls for shell-runner rollout decisions. */
  shell_runner_governance: skillsShellRunnerGovernanceSchema,
  /** Skills watcher settings. */
  load: skillsLoadSchema,
}).default({});

const mcpServerSchema = z.object({
  name: z.string(),
  command: z.string(),
  args: z.array(z.string()).default([]),
  env: z.record(z.string(), z.string()).optional(),
  cwd: z.string().optional(),
});

const mcpSchema = z.object({
  servers: z.array(mcpServerSchema).default([]),
}).default({ servers: [] });

const modelTierEnum = z.enum(['fast', 'default', 'complex', 'local']);

const cronJobSchema = z.object({
  name: z.string().min(1, 'Cron job name is required'),
  schedule: z.string().min(1, 'Cron schedule is required'),
  message: z.string().min(1, 'Cron message is required'),
  output: z.object({
    channel: z.string().min(1),
    peer: z.string().min(1),
  }),
  enabled: z.boolean().default(true),
  timezone: z.string().optional(),
  model_tier: modelTierEnum.optional(),
});

const webhookSchema = z.object({
  name: z.string().min(1, 'Webhook name is required'),
  secret: z.string().optional(),
  message: z.string().default('{{body}}'),
  output: z.object({
    channel: z.string().min(1),
    peer: z.string().min(1),
  }),
  enabled: z.boolean().default(true),
});

const gmailSchema = z.object({
  enabled: z.boolean().default(false),
  credentials_file: z.string().optional(),
  token_file: z.string().default('~/.config/flynn/gmail-token.json'),
  /**
   * Optional Google Cloud Pub/Sub topic for Gmail push notifications.
   * Format: projects/<project-id>/topics/<topic>
   * If omitted, push notifications are disabled and Flynn will use polling.
   */
  pubsub_topic: z.string().optional(),

  /**
   * Explicitly disable Gmail push watch registration even if pubsub_topic is set.
   * Useful for environments where Google cannot reach the gateway (e.g. tailnet-only).
   */
  disable_push: z.boolean().default(false),

  /**
   * Optional Pub/Sub subscription for pull-based delivery (no inbound webhook required).
   * Format: projects/<project-id>/subscriptions/<subscription>
   */
  pubsub_subscription_id: z.string().optional(),

  /** How often to pull messages from pubsub_subscription_id (e.g. '60s'). */
  pubsub_pull_interval: z.string().default('60s'),

  /** Max messages to pull per cycle (1..100). */
  pubsub_max_messages: z.number().min(1).max(100).default(10),
  watch_labels: z.array(z.string()).default(['INBOX']),
  poll_interval: z.string().default('300s'),
  history_start: z.string().optional(),  // ISO date string — only process emails after this date
  output: z.object({
    channel: z.string().min(1),
    peer: z.string().min(1),
  }),
  message: z.string().default('New email from {{from}}: {{subject}}\n\n{{snippet}}'),
}).optional();

const heartbeatCheckSchema = z.enum(['gateway', 'model', 'channels', 'memory', 'disk']);

const heartbeatSchema = z.object({
  enabled: z.boolean().default(false),
  interval: z.string().default('5m'),
  checks: z.array(heartbeatCheckSchema).default(['gateway', 'model', 'channels', 'memory', 'disk']),
  notify: z.object({
    channel: z.string().min(1),
    peer: z.string().min(1),
  }).optional(),
  failure_threshold: z.number().min(1).max(10).default(2),
  disk_threshold_mb: z.number().min(10).default(100),
}).default({});

const gcalSchema = z.object({
  enabled: z.boolean().default(false),
  credentials_file: z.string().optional(),
  token_file: z.string().default('~/.config/flynn/gcal-token.json'),
  calendar_ids: z.array(z.string()).default(['primary']),
}).optional();

const gdocsSchema = z.object({
  enabled: z.boolean().default(false),
  credentials_file: z.string().optional(),
  token_file: z.string().default('~/.config/flynn/gdocs-token.json'),
}).optional();

const gdriveSchema = z.object({
  enabled: z.boolean().default(false),
  credentials_file: z.string().optional(),
  token_file: z.string().default('~/.config/flynn/gdrive-token.json'),
}).optional();

const gtasksSchema = z.object({
  enabled: z.boolean().default(false),
  credentials_file: z.string().optional(),
  token_file: z.string().default('~/.config/flynn/gtasks-token.json'),
}).optional();

const automationSchema = z.object({
  cron: z.array(cronJobSchema).default([]),
  webhooks: z.array(webhookSchema).default([]),
  gmail: gmailSchema,
  gcal: gcalSchema,
  gdocs: gdocsSchema,
  gdrive: gdriveSchema,
  gtasks: gtasksSchema,
  heartbeat: heartbeatSchema,
}).default({});

const truthfulnessModeSchema = z.enum(['strict', 'standard', 'relaxed']);
const autonomyLevelSchema = z.enum(['conservative', 'standard', 'autonomous']);

const agentsSchema = z.object({
  primary_tier: z.enum(['fast', 'default', 'complex', 'local']).default('default'),
  delegation: z.object({
    compaction: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
    memory_extraction: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
    classification: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
    tool_summarisation: z.enum(['fast', 'default', 'complex', 'local']).default('fast'),
    complex_reasoning: z.enum(['fast', 'default', 'complex', 'local']).default('complex'),
  }).default({
    compaction: 'fast',
    memory_extraction: 'fast',
    classification: 'fast',
    tool_summarisation: 'fast',
    complex_reasoning: 'complex',
  }),
  auto_escalate: z.boolean().default(false),
  max_delegation_depth: z.number().min(1).max(10).default(3),
  /** Maximum tool-loop iterations before the agent stops. */
  max_iterations: z.number().min(1).max(50).default(10),
  /** Truthfulness enforcement level: strict | standard | relaxed. */
  truthfulness_mode: truthfulnessModeSchema.default('standard'),
  /** Autonomy level for tool execution: conservative | standard | autonomous. */
  autonomy_level: autonomyLevelSchema.default('standard'),
}).default({});

const embeddingProviderSchema = z.enum(['openai', 'gemini', 'ollama', 'llamacpp', 'voyage']);

const embeddingSchema = z.object({
  enabled: z.boolean().default(false),
  provider: embeddingProviderSchema.default('openai'),
  model: z.string().default('text-embedding-3-small'),
  endpoint: z.string().optional(),
  api_key: z.string().optional(),
  dimensions: z.number().optional(),
  chunk_size: z.number().min(64).max(8192).default(512),
  chunk_overlap: z.number().min(0).max(1024).default(50),
  top_k: z.number().min(1).max(50).default(5),
  hybrid_weight: z.number().min(0).max(1).default(0.7),
}).default({});

const memorySchema = z.object({
  enabled: z.boolean().default(true),
  dir: z.string().optional(), // Default: ~/.local/share/flynn/memory
  auto_extract: z.boolean().default(true),
  injection_strategy: z.enum(['all', 'recent', 'adaptive']).default('all'),
  max_injection_tokens: z.number().min(100).max(10000).default(2000),
  max_context_tokens: z.number().min(100).max(10000).default(2000),
  embedding: embeddingSchema,
}).default({});

const compactionSchema = z.object({
  enabled: z.boolean().default(true),
  threshold_pct: z.number().min(10).max(100).default(80),
  keep_turns: z.number().min(1).max(50).default(4),
  summary_max_tokens: z.number().min(128).max(4096).default(1024),
  importance_threshold: z.number().min(0).max(1).default(1),
}).default({});

const discordSchema = z.object({
  bot_token: z.string().min(1, 'Bot token is required'),
  allowed_guild_ids: z.array(z.string()).default([]),
  allowed_channel_ids: z.array(z.string()).default([]),
  require_mention: z.boolean().default(true),
}).optional();

const slackSchema = z.object({
  bot_token: z.string().min(1, 'Bot token is required'),
  app_token: z.string().min(1, 'App token is required'),
  signing_secret: z.string().min(1, 'Signing secret is required'),
  allowed_channel_ids: z.array(z.string()).default([]),
  require_mention: z.boolean().default(false),
}).optional();

const whatsappSchema = z.object({
  allowed_numbers: z.array(z.string()).default([]),
  allowed_group_ids: z.array(z.string()).default([]),
  require_mention: z.boolean().default(true),
  data_dir: z.string().optional(),
}).optional();

const matrixSchema = z.object({
  homeserver_url: z.string().url('Homeserver URL is required'),
  access_token: z.string().min(1, 'Access token is required'),
  allowed_room_ids: z.array(z.string()).default([]),
  require_mention: z.boolean().default(true),
  sync_timeout_ms: z.number().min(1000).max(120000).default(30000),
  display_name: z.string().optional(),
}).optional();

const browserSchema = z.object({
  enabled: z.boolean().default(false),
  executable_path: z.string().optional(),
  ws_endpoint: z.string().optional(),
  headless: z.boolean().default(true),
  max_pages: z.number().min(1).max(20).default(5),
  default_timeout: z.number().min(1000).max(120000).default(30000),
}).default({});

const processSchema = z.object({
  max_concurrent: z.number().min(1).max(50).default(10),
  max_runtime_minutes: z.number().min(1).max(1440).default(60),
  buffer_size: z.number().min(1024).max(1048576).default(65536),
}).default({});

const retrySchema = z.object({
  enabled: z.boolean().default(true),
  max_retries: z.number().min(0).max(10).default(3),
  initial_delay_ms: z.number().min(100).max(60000).default(1000),
  backoff_multiplier: z.number().min(1).max(5).default(2),
  max_delay_ms: z.number().min(1000).max(120000).default(30000),
}).default({});

const webSearchSchema = z.object({
  provider: z.enum(['brave', 'searxng']).default('brave'),
  api_key: z.string().optional(),
  endpoint: z.string().optional(),
  max_results: z.number().min(1).max(20).default(5),
}).default({});

const audioProviderSchema = z.object({
  type: z.enum(['openai', 'groq', 'ollama', 'llamacpp', 'custom']),
  endpoint: z.string().optional(),
  api_key: z.string().optional(),
  model: z.string().optional(),
});

const audioSchema = z.object({
  enabled: z.boolean().default(false),
  provider: audioProviderSchema.optional(),
}).default({});

// ── Tool policy schemas ──────────────────────────────────────────────

const toolProfileEnum = z.enum(['minimal', 'messaging', 'coding', 'full']);

const toolOverrideSchema = z.object({
  profile: toolProfileEnum.optional(),
  allow: z.array(z.string()).default([]),
  deny: z.array(z.string()).default([]),
}).default({});

const toolsSchema = z.object({
  profile: toolProfileEnum.default('full'),
  allow: z.array(z.string()).default([]),
  deny: z.array(z.string()).default([]),
  agents: z.record(z.string(), toolOverrideSchema).default({}),
  providers: z.record(z.string(), toolOverrideSchema).default({}),
}).default({});

// ── Sandbox schemas ───────────────────────────────────────────────────

const sandboxSchema = z.object({
  enabled: z.boolean().default(false),
  image: z.string().default('node:22-slim'),
  workspace_dir: z.string().default('/workspace'),
  network: z.enum(['none', 'bridge', 'host']).default('none'),
  memory_limit: z.string().default('512m'),
  cpu_limit: z.string().default('1.0'),
  timeout_seconds: z.number().min(10).max(3600).default(300),
}).default({});

// ── Agent config + routing schemas ────────────────────────────────────

const agentConfigEntrySchema = z.object({
  system_prompt: z.string().optional(),
  model_tier: modelTierEnum.optional(),
  tool_profile: toolProfileEnum.optional(),
  tool_overrides: toolOverrideSchema.optional(),
  sandbox: z.boolean().default(false),
});

const agentConfigsSchema = z.record(z.string(), agentConfigEntrySchema).default({});

const routingSchema = z.object({
  default_agent: z.string().optional(),
  channels: z.record(z.string(), z.string()).default({}),
  senders: z.record(z.string(), z.string()).default({}),
}).default({});

const intentTargetTypeSchema = z.enum(['agent', 'skill']);

const intentRuleSchema = z.object({
  name: z.string().min(1),
  patterns: z.array(z.string().min(1)).min(1),
  target: z.object({
    type: intentTargetTypeSchema,
    name: z.string().min(1),
  }),
  priority: z.number().default(0),
  enabled: z.boolean().default(true),
});

const intentsSchema = z.object({
  enabled: z.boolean().default(false),
  match_threshold: z.number().min(0).max(1).default(0.7),
  rules: z.array(intentRuleSchema).default([]),
}).default({});

const routingPolicySchema = z.object({
  enabled: z.boolean().default(false),
  fast_path_threshold: z.number().min(0).max(1).default(0.85),
  llm_threshold: z.number().min(0).max(1).default(0.5),
  default_path: z.enum(['fast', 'llm']).default('llm'),
}).default({});

const contextLevelSchema = z.enum(['minimal', 'normal', 'detailed', 'debug']);

const promptSchema = z.object({
  /** Additional directories to search for prompt template files. */
  search_dirs: z.array(z.string()).default([]),
  /** Extra named sections to include in the system prompt. */
  extra_sections: z.array(z.object({
    name: z.string(),
    content: z.string(),
  })).default([]),
  /** Prompt context depth control: minimal | normal | detailed | debug. */
  context_level: contextLevelSchema.default('normal'),
}).default({});

const sessionsSchema = z.object({
  ttl: z.string().default('30d'),
}).default({});

const historyIndexSchema = z.object({
  enabled: z.boolean().default(false),
  max_keywords: z.number().min(1).max(20).default(8),
  search_limit: z.number().min(1).max(100).default(10),
  min_score: z.number().min(0).max(1).default(0.15),
  routing_boost: z.number().min(0).max(0.2).default(0.05),
}).default({});

const logLevelSchema = z.enum(['debug', 'info', 'warn', 'error', 'silent']).default('info');

const auditLevelSchema = z.enum(['debug', 'info', 'warn', 'error']).default('debug');

const auditSchema = z.object({
  enabled: z.boolean().default(true),
  path: z.string().default('~/.local/share/flynn/audit.log'),
  max_size_mb: z.number().min(1).max(1000).default(10),
  keep_days: z.number().min(1).max(365).default(30),
  levels: z.object({
    tools: auditLevelSchema.default('debug'),
    sessions: auditLevelSchema.default('debug'),
    automation: auditLevelSchema.default('debug'),
  }).default({}),
}).default({});

export const configSchema = z.object({
  log_level: logLevelSchema,
  audit: auditSchema,
  telegram: telegramSchema.optional(),
  discord: discordSchema,
  slack: slackSchema,
  whatsapp: whatsappSchema,
  matrix: matrixSchema,
  server: serverSchema.default({}),
  models: modelsSchema,
  backends: backendsSchema.default({}),
  hooks: hooksSchema.default({}),
  skills: skillsSchema.default({}),
  mcp: mcpSchema.default({ servers: [] }),
  automation: automationSchema,
  agents: agentsSchema,
  compaction: compactionSchema,
  memory: memorySchema,
  process: processSchema,
  browser: browserSchema,
  retry: retrySchema,
  web_search: webSearchSchema,
  audio: audioSchema,
  prompt: promptSchema,
  tools: toolsSchema,
  sandbox: sandboxSchema,
  agent_configs: agentConfigsSchema,
  routing: routingSchema,
  intents: intentsSchema,
  routing_policy: routingPolicySchema,
  history_index: historyIndexSchema,
  sessions: sessionsSchema,
  pairing: pairingSchema,
});

export type Config = z.infer<typeof configSchema>;
export type TelegramConfig = z.infer<typeof telegramSchema>;
export type ModelConfig = z.infer<typeof modelConfigSchema>;
export type CronJobConfig = z.infer<typeof cronJobSchema>;
export type WebhookConfig = z.infer<typeof webhookSchema>;
export type GmailConfig = z.infer<typeof gmailSchema>;
export type AgentsConfig = z.infer<typeof agentsSchema>;
export type CompactionConfig = z.infer<typeof compactionSchema>;
export type MemoryConfig = z.infer<typeof memorySchema>;
export type WebSearchConfig = z.infer<typeof webSearchSchema>;
export type AudioConfig = z.infer<typeof audioSchema>;
export type ProcessConfig = z.infer<typeof processSchema>;
export type BrowserConfig = z.infer<typeof browserSchema>;
export type DiscordConfig = z.infer<typeof discordSchema>;
export type SlackConfig = z.infer<typeof slackSchema>;
export type WhatsAppConfig = z.infer<typeof whatsappSchema>;
export type MatrixConfig = z.infer<typeof matrixSchema>;
export type RetryPolicyConfig = z.infer<typeof retrySchema>;
export type ContextLevel = z.infer<typeof contextLevelSchema>;
export type PromptConfig = z.infer<typeof promptSchema>;
export type ToolProfile = z.infer<typeof toolProfileEnum>;
export type ToolOverrideConfig = z.infer<typeof toolOverrideSchema>;
export type ToolsConfig = z.infer<typeof toolsSchema>;
export type SandboxConfig = z.infer<typeof sandboxSchema>;
export type AgentConfigEntry = z.infer<typeof agentConfigEntrySchema>;
export type RoutingConfig = z.infer<typeof routingSchema>;
export type IntentTargetType = z.infer<typeof intentTargetTypeSchema>;
export type IntentRuleConfig = z.infer<typeof intentRuleSchema>;
export type IntentsConfig = z.infer<typeof intentsSchema>;
export type RoutingPolicyConfig = z.infer<typeof routingPolicySchema>;
export type HistoryIndexConfig = z.infer<typeof historyIndexSchema>;
export type ServerConfig = z.infer<typeof serverSchema>;
export type SessionsConfig = z.infer<typeof sessionsSchema>;
export type ThinkingConfig = z.infer<typeof thinkingSchema>;
export type HeartbeatConfig = z.infer<typeof heartbeatSchema>;
export type HeartbeatCheck = z.infer<typeof heartbeatCheckSchema>;
export type EmbeddingConfig = z.infer<typeof embeddingSchema>;
export type EmbeddingProvider = z.infer<typeof embeddingProviderSchema>;
export type GcalConfig = z.infer<typeof gcalSchema>;
export type GdocsConfig = z.infer<typeof gdocsSchema>;
export type GdriveConfig = z.infer<typeof gdriveSchema>;
export type GtasksConfig = z.infer<typeof gtasksSchema>;
export type PairingCodeConfig = z.infer<typeof pairingSchema>;
export type LogLevel = z.infer<typeof logLevelSchema>;
export type AuditConfig = z.infer<typeof auditSchema>;
export type AuditLevel = z.infer<typeof auditLevelSchema>;
export type TruthfulnessMode = z.infer<typeof truthfulnessModeSchema>;
export type AutonomyLevel = z.infer<typeof autonomyLevelSchema>;
