/**
 * Generic serialization system for resource definitions
 * Automatically handles Zod schemas, Maps, Sets, functions, and special types (Tools, KnowledgeMap)
 *
 * Replaces manual field extraction with automatic type detection and conversion.
 * Used for API responses, CLI exports, and testing fixtures.
 */

import { zodToJsonSchema } from '@alcyone-labs/zod-to-json-schema'
import type { AgentDefinition } from '../agent/core/types'
import type { WorkflowDefinition, NextConfig } from '../workflow/types'
import { StepType } from '../workflow/types'

/**
 * Serialization options for different use cases
 */
export interface SerializationOptions {
  /** Redact secrets (apiKey, tokens, passwords) - Default: true */
  redactSecrets?: boolean
  /** Include tool schemas in serialized tools - Default: true */
  includeToolSchemas?: boolean
  /** Include knowledge map metadata - Default: true */
  includeKnowledgeMap?: boolean
}

/**
 * Internal serialization context (includes circular reference tracking)
 */
interface SerializationContext extends Required<SerializationOptions> {
  /** Circular reference protection via WeakSet */
  visited: WeakSet<object>
}

/**
 * Main entry point: Serialize any resource definition to JSON-safe format
 *
 * Automatically converts:
 * - Zod schemas → JSON Schema
 * - Maps → arrays with id field
 * - Sets → arrays
 * - Dates → ISO strings
 * - Functions → removed
 * - Secrets → redacted
 *
 * @param definition - Resource definition (AgentDefinition or WorkflowDefinition)
 * @param options - Serialization options
 * @returns JSON-serializable object
 */
export function serializeDefinition(
  definition: AgentDefinition | WorkflowDefinition,
  options?: SerializationOptions
): any {
  const opts: Required<SerializationOptions> = {
    redactSecrets: true,
    includeToolSchemas: true,
    includeKnowledgeMap: true,
    ...options
  }

  const context: SerializationContext = {
    ...opts,
    visited: new WeakSet()
  }

  return deepSerialize(definition, context)
}

/**
 * Recursive serialization with type detection
 * Handles all special types and delegates to specialized handlers
 */
function deepSerialize(value: any, ctx: SerializationContext): any {
  // Primitives & null/undefined
  if (value === null || value === undefined) return value
  if (typeof value !== 'object') return value

  // Zod schemas are leaf values — always convert (no circular tracking needed)
  if (isZodSchema(value)) return convertZodToJsonSchema(value)

  // Circular reference check (for non-leaf objects only)
  if (ctx.visited.has(value)) {
    return { __circular: true }
  }
  ctx.visited.add(value)
  if (value instanceof Map) return convertMapToArray(value, ctx)
  if (value instanceof Set) return convertSetToArray(value, ctx)
  if (value instanceof Date) return value.toISOString()
  if (typeof value === 'function') return undefined // Remove functions

  // Arrays
  if (Array.isArray(value)) {
    return value.map((item) => deepSerialize(item, ctx)).filter((item) => item !== undefined)
  }

  // Objects - special handling for known types
  if (isToolObject(value)) return serializeTool(value, ctx)
  if (isKnowledgeMapObject(value)) return serializeKnowledgeMap(value, ctx)
  if (isNextConfigObject(value)) return serializeNextConfig(value, ctx)

  // Generic object recursion
  return serializeObject(value, ctx)
}

/**
 * Serialize generic object (recursively process all properties)
 * Handles secret redaction and function removal
 */
function serializeObject(obj: any, ctx: SerializationContext): any {
  const result: any = {}

  for (const [key, val] of Object.entries(obj)) {
    if (typeof val === 'function') continue

    // Redact secrets
    if (ctx.redactSecrets && isSecretKey(key)) {
      result[key] = redactSecret(val)
      continue
    }

    // Record-based steps → array format (backward compat with Map-based steps)
    if (key === 'steps' && isWorkflowStepsRecord(val)) {
      result[key] = convertRecordToArray(val as Record<string, any>, ctx)
      continue
    }

    // Recurse
    const serialized = deepSerialize(val, ctx)
    if (serialized !== undefined) {
      result[key] = serialized
    }
  }

  return result
}

/**
 * Serialize Tool object
 * Extracts name, description, and optional schemas
 */
function serializeTool(tool: any, ctx: SerializationContext): any {
  const result: any = {
    name: tool.name,
    description: tool.description
  }

  // Include schemas if requested
  if (ctx.includeToolSchemas && tool.inputSchema) {
    result.inputSchema = deepSerialize(tool.inputSchema, ctx)
  }

  if (ctx.includeToolSchemas && tool.outputSchema) {
    result.outputSchema = deepSerialize(tool.outputSchema, ctx)
  }

  // execute function automatically omitted (typeof === 'function')

  return result
}

/**
 * Serialize KnowledgeMap object
 * Extracts node metadata without content or file paths
 */
function serializeKnowledgeMap(km: any, ctx: SerializationContext): any {
  if (!ctx.includeKnowledgeMap) return undefined

  // Handle nodes field (Record<string, KnowledgeNode>)
  const nodes = km.nodes || {}
  const nodeEntries = Object.entries(nodes).map(([id, node]: [string, any]) => ({
    id,
    description: node.description,
    loaded: node.loaded || false,
    hasPrompt: Boolean(node.prompt)
    // load function automatically omitted
    // prompt content omitted (too large for structure metadata)
  }))

  return {
    nodeCount: nodeEntries.length,
    nodes: nodeEntries
  }
}

/**
 * Serialize NextConfig object
 * Strips non-serializable condition functions while preserving route structure
 */
function serializeNextConfig(nextConfig: NextConfig, _context: SerializationContext): any {
  if (!nextConfig) return null

  // Handle linear next (simple target)
  if (nextConfig.type === StepType.LINEAR) {
    return {
      type: nextConfig.type,
      target: nextConfig.target
    }
  }

  // Handle conditional next (strip condition functions)
  if (nextConfig.type === StepType.CONDITIONAL) {
    return {
      type: nextConfig.type,
      routes:
        nextConfig.routes?.map((route) => ({
          // Strip condition function, keep only target
          target: route.target
        })) ?? [],
      default: nextConfig.default
    }
  }

  return null
}

/**
 * Convert Map to array with id field
 */
function convertMapToArray(map: Map<any, any>, ctx: SerializationContext): any[] {
  return Array.from(map.entries()).map(([key, val]) => ({
    id: key,
    ...deepSerialize(val, ctx)
  }))
}

/**
 * Check if value is a Record of WorkflowStep objects (plain object with step-shaped values)
 * Used to detect Record<string, WorkflowStep> after the Map-to-Record refactor
 */
function isWorkflowStepsRecord(value: any): boolean {
  if (!value || typeof value !== 'object' || Array.isArray(value) || value instanceof Map || value instanceof Set) {
    return false
  }
  const entries = Object.values(value)
  if (entries.length === 0) return false
  // Check that at least the first entry looks like a WorkflowStep (has name + handler)
  const first = entries[0] as any
  return first && typeof first === 'object' && 'name' in first && 'handler' in first
}

/**
 * Convert Record<string, WorkflowStep> to array with id field
 * Produces the same format as convertMapToArray for backward compatibility
 */
function convertRecordToArray(record: Record<string, any>, ctx: SerializationContext): any[] {
  return Object.entries(record).map(([key, val]) => ({
    id: key,
    ...deepSerialize(val, ctx)
  }))
}

/**
 * Convert Set to array
 */
function convertSetToArray(set: Set<any>, ctx: SerializationContext): any[] {
  return Array.from(set).map((v) => deepSerialize(v, ctx))
}

/**
 * Convert Zod schema to JSON Schema
 * Reuses existing utility function
 */
function convertZodToJsonSchema(schema: any): any {
  return zodToJsonSchema(schema, {
    $refStrategy: 'none',
    errorMessages: true
  })
}

/**
 * Type guard: Check if value is a Zod schema
 */
function isZodSchema(value: any): boolean {
  return value && typeof value === 'object' && '_def' in value && 'parse' in value
}

/**
 * Type guard: Check if value is a Tool object
 */
function isToolObject(value: any): boolean {
  return value && typeof value === 'object' && 'name' in value && 'description' in value && 'execute' in value
}

/**
 * Type guard: Check if value is a KnowledgeMap object
 */
function isKnowledgeMapObject(value: any): boolean {
  return value && typeof value === 'object' && 'nodes' in value && typeof value.nodes === 'object'
}

/**
 * Type guard: Check if value is a NextConfig object
 */
function isNextConfigObject(value: any): boolean {
  return (
    value &&
    typeof value === 'object' &&
    'type' in value &&
    (value.type === StepType.LINEAR || value.type === StepType.CONDITIONAL)
  )
}

/**
 * Check if key name indicates secret data
 */
function isSecretKey(key: string): boolean {
  const lower = key.toLowerCase()

  // Whitelist: keys that contain "token" but are not secrets
  const whitelist = ['maxtokens', 'memorytokens', 'inputtokens', 'outputtokens']
  if (whitelist.some((w) => lower.includes(w))) return false

  const patterns = ['apikey', 'secret', 'password', 'token', 'credential']
  return patterns.some((p) => lower.includes(p))
}

/**
 * Redact secret value while preserving prefix
 * Example: 'sk-proj-abc123...' → 'sk-proj...'
 */
function redactSecret(value: any): string {
  if (typeof value !== 'string') return '[REDACTED]'
  if (value.length <= 7) return '[REDACTED]'
  return value.substring(0, 7) + '...'
}
