/**
 * Common validation utilities
 *
 * Reusable Zod schema primitives for input validation across all domains.
 * These validators are composed by domain-specific api-schemas.ts files.
 *
 * Design Principles:
 * - KISS: Pure functions, no classes, no state
 * - DRY: Common patterns extracted once, reused everywhere
 * - Type Safety: Full TypeScript inference, no any types
 * - Composability: Small validators easily combined using Zod methods
 *
 * Security Benefits:
 * - SQL Injection Prevention: Strict format validation (UUID, Email)
 * - DoS Protection: Max length constraints on strings/arrays, pagination limits
 * - Mass Assignment Prevention: Use .strict() mode on all request schemas
 * - XSS Prevention: Input sanitization and format validation
 *
 * @module validation
 */

import { z } from 'zod'

/**
 * UUID validation schema
 * Ensures string is a valid UUID (v4 format)
 */
export const UuidSchema = z.string().uuid()

/**
 * Non-empty string validation schema
 * Trims whitespace and enforces minimum 1 character, maximum 1000 characters
 */
export const NonEmptyStringSchema = z.string().trim().min(1).max(1000)

/**
 * Resource type validation
 * Used across execution systems for workflow and agent resources
 */
export const ResourceTypeSchema = z.enum(['agent', 'workflow'])

/**
 * Origin resource type validation
 * Includes all possible execution origins for audit trails
 */
export const OriginResourceTypeSchema = z.enum(['agent', 'workflow', 'scheduler', 'api'])

/**
 * Creates a payload size validator with configurable max size
 * Validates that JSON serialization of payload does not exceed size limit
 *
 * @param maxSizeBytes - Maximum allowed size in bytes (e.g., 500_000 for 500KB)
 * @param options - Configuration options
 * @param options.optional - If false, undefined values are rejected (default: false)
 * @returns Zod schema that validates payload size
 *
 * @example
 * ```typescript
 * const PayloadSchema = createPayloadSizeValidator(500_000) // Required, 500KB limit
 * const OptionalPayloadSchema = createPayloadSizeValidator(500_000, { optional: true })
 * PayloadSchema.parse({ data: 'test' }) // OK
 * PayloadSchema.parse({ data: 'x'.repeat(600_000) }) // Fails - too large
 * PayloadSchema.parse(undefined) // Fails - required
 * OptionalPayloadSchema.parse(undefined) // OK - optional
 * ```
 */
export function createPayloadSizeValidator(maxSizeBytes: number, options?: { optional?: boolean }) {
  const allowUndefined = options?.optional === true

  return z.unknown().superRefine((val, ctx) => {
    // Check if value is undefined
    if (val === undefined) {
      if (!allowUndefined) {
        ctx.addIssue({
          code: 'custom',
          message: 'Payload is required'
        })
      }
      return // Stop validation if undefined
    }

    // Null is treated as valid empty payload
    if (val === null) {
      return
    }

    // Check size
    const size = JSON.stringify(val).length
    if (size > maxSizeBytes) {
      ctx.addIssue({
        code: 'custom',
        message: `Payload exceeds maximum size of ${Math.round(maxSizeBytes / 1000)}KB`
      })
    }
  })
}

/**
 * Credential name validation
 * - Lowercase letters, numbers, and hyphens only
 * - Must contain at least one hyphen (enforces {service}-{env} convention)
 * - No sequential hyphens, no leading/trailing hyphens
 * - 1-100 characters
 * - Input is auto-lowercased and trimmed
 *
 * SECURITY: Prevents path traversal attacks on /decrypt endpoint
 * Rejects: '../admin-cred', 'test@prod', 'gmail prod', 'Gmail_Prod'
 *
 * Valid: 'gmail-prod', 'attio-dev', 'stripe-api-key'
 * Invalid: 'GmailProd', 'gmail_prod', 'gmail--prod', 'gmail', '-gmail-prod'
 */
export const CredentialNameSchema = z
  .string()
  .trim()
  .toLowerCase()
  .min(1, 'Credential name required')
  .max(100, 'Credential name too long (max 100 chars)')
  .regex(
    /^[a-z0-9]+(-[a-z0-9]+)+$/,
    'Credential name must be lowercase letters, numbers, and hyphens in format: service-environment (e.g., gmail-prod, attio-dev)'
  )

/**
 * Organization ID validation (UUID format)
 */
export const OrganizationIdSchema = UuidSchema

/**
 * OAuth provider validation
 * Must match providers in provider-registry.ts
 */
export const OAuthProviderSchema = z.enum(['google-sheets', 'google-calendar', 'dropbox'])

/**
 * OAuth authorization code validation
 * Typical OAuth codes are 20-500 characters
 */
export const OAuthCodeSchema = z
  .string()
  .min(10, 'Authorization code too short')
  .max(1000, 'Authorization code too long')

/**
 * OAuth state parameter validation
 * Base64-encoded JSON state object, max 2KB
 */
export const OAuthStateParamSchema = z
  .string()
  .min(10, 'State parameter too short')
  .max(2048, 'State parameter too long')

/**
 * Sanitized string (removes dangerous characters)
 */
export const SanitizedStringSchema = z
  .string()
  .trim()
  .transform((str) => str.replace(/[<>'"]/g, ''))

/**
 * Validates email format (RFC 5322)
 *
 * Security: Prevents email header injection, validates format before sending
 *
 * @example
 * EmailSchema.parse('user@example.com') // OK
 * EmailSchema.parse('invalid') // Error: Invalid email
 */
export const EmailSchema = z.string().email()

/**
 * Validates URL format (HTTP/HTTPS)
 *
 * Security: Prevents open redirect attacks, validates callback/webhook URLs
 *
 * @example
 * UrlSchema.parse('https://example.com') // OK
 * UrlSchema.parse('not-a-url') // Error: Invalid URL
 *
 * @example
 * // HTTPS only
 * const SecureUrlSchema = UrlSchema.refine(
 *   (url) => url.startsWith('https://'),
 *   { message: 'HTTPS required' }
 * )
 */
export const UrlSchema = z.string().url()

/**
 * Standard pagination parameters
 *
 * Constraints:
 * - limit: 1-100 items (default: 20)
 * - offset: 0+ items (default: 0)
 *
 * Uses z.coerce to handle string query parameters from URLs
 *
 * Security: DoS protection (max 100 items), prevents type coercion exploits
 *
 * @example
 * // Query string: ?limit=50&offset=100
 * PaginationSchema.parse({ limit: '50', offset: '100' })
 * // Result: { limit: 50, offset: 100 }
 *
 * @example
 * // Extend with filters
 * const FilteredListSchema = PaginationSchema.extend({
 *   status: z.enum(['active', 'inactive']),
 *   search: z.string().optional()
 * })
 */
export const PaginationSchema = z.object({
  limit: z.coerce.number().int().min(1).max(100).default(20),
  offset: z.coerce.number().int().min(0).default(0)
})

/**
 * Validates ISO 8601 datetime format
 *
 * Security: Prevents date parsing exploits, ensures consistent timezone handling
 *
 * @example
 * TimestampSchema.parse('2025-11-13T10:30:00Z') // OK
 * TimestampSchema.parse('invalid-date') // Error: Invalid datetime
 */
export const TimestampSchema = z.string().datetime()

/**
 * Date range with start and end timestamps
 *
 * Note: Does not validate that end > start by default.
 * Use .refine() for logical validation.
 *
 * Security: Prevents time-based attacks, validates report ranges
 *
 * @example
 * const schema = DateRangeSchema.refine(
 *   (data) => new Date(data.endDate) > new Date(data.startDate),
 *   { message: 'End date must be after start date' }
 * )
 */
export const DateRangeSchema = z.object({
  startDate: z.string().datetime(),
  endDate: z.string().datetime()
})

/**
 * Creates an enum schema with custom error message
 *
 * @param values - Array of valid enum values
 * @param errorMessage - Optional custom error message
 *
 * @example
 * const StatusSchema = createEnumSchema(
 *   ['active', 'inactive', 'pending'],
 *   'Status must be active, inactive, or pending'
 * )
 *
 * const taskSchema = z.object({
 *   status: StatusSchema
 * })
 */
export function createEnumSchema<T extends [string, ...string[]]>(values: T, errorMessage?: string) {
  const schema = z.enum(values)
  return errorMessage ? schema.describe(errorMessage) : schema
}

/**
 * Creates a string schema with custom length constraints
 *
 * @param minLength - Minimum string length
 * @param maxLength - Maximum string length
 * @param fieldName - Optional field name for error messages
 *
 * @example
 * const UsernameSchema = createStringSchema(3, 20, 'Username')
 * const BioSchema = createStringSchema(0, 500, 'Bio')
 *
 * const userSchema = z.object({
 *   username: UsernameSchema,
 *   bio: BioSchema.optional()
 * })
 */
export function createStringSchema(minLength: number, maxLength: number, fieldName?: string): z.ZodString {
  const schema = z.string().trim().min(minLength).max(maxLength)
  return fieldName ? schema.describe(`${fieldName} (${minLength}-${maxLength} characters)`) : schema
}

/**
 * Creates an array schema with size constraints
 *
 * @param itemSchema - Zod schema for array items
 * @param minItems - Minimum array size
 * @param maxItems - Maximum array size
 * @param fieldName - Optional field name for error messages
 *
 * @example
 * const TagsSchema = createArraySchema(
 *   z.string(),
 *   1,
 *   10,
 *   'Tags'
 * )
 *
 * const EmailListSchema = createArraySchema(
 *   EmailSchema,
 *   1,
 *   5,
 *   'Email addresses'
 * )
 *
 * const articleSchema = z.object({
 *   tags: TagsSchema,
 *   notifyEmails: EmailListSchema
 * })
 */
export function createArraySchema<T extends z.ZodTypeAny>(
  itemSchema: T,
  minItems: number,
  maxItems: number,
  fieldName?: string
): z.ZodArray<T> {
  const schema = z.array(itemSchema).min(minItems).max(maxItems)
  return fieldName ? schema.describe(`${fieldName} (${minItems}-${maxItems} items)`) : schema
}

/**
 * Formatted validation error response
 * Used by API error handler to return structured validation errors
 */
export interface ValidationErrorResponse {
  message: string
  fields: Record<string, string[]>
}

/**
 * Format Zod validation errors into structured, user-friendly messages
 *
 * Transforms Zod's nested error structure into a flat field-to-errors mapping
 * for better API consumer experience. Shows ALL validation errors at once.
 *
 * Security: Sanitizes internal schema details while preserving actionable feedback
 *
 * @param error - ZodError from failed validation
 * @returns Structured error response with message and field-level errors
 *
 * @example
 * ```typescript
 * const schema = z.object({
 *   email: EmailSchema,
 *   age: z.number().min(18)
 * })
 *
 * try {
 *   schema.parse({ email: 'invalid', age: 15 })
 * } catch (error) {
 *   const formatted = formatZodValidationError(error as ZodError)
 *   // {
 *   //   message: 'Validation failed',
 *   //   fields: {
 *   //     email: ['Invalid email'],
 *   //     age: ['Number must be greater than or equal to 18']
 *   //   }
 *   // }
 * }
 * ```
 *
 * @example
 * ```typescript
 * // Nested object validation
 * const schema = z.object({
 *   user: z.object({
 *     profile: z.object({
 *       email: EmailSchema
 *     })
 *   })
 * })
 *
 * // Error path: user.profile.email
 * // Output: { 'user.profile.email': ['Invalid email'] }
 * ```
 */
export function formatZodValidationError(error: z.ZodError): ValidationErrorResponse {
  const fieldErrors: Record<string, string[]> = {}

  for (const issue of error.issues) {
    // Build field path (e.g., 'user.email' or 'items[0].name')
    const path = issue.path.length > 0 ? issue.path.join('.') : '_root'

    // Initialize field error array if not exists
    if (!fieldErrors[path]) {
      fieldErrors[path] = []
    }

    // Add error message (Zod provides good messages by default)
    fieldErrors[path].push(issue.message)
  }

  // Count total errors for summary message
  const errorCount = Object.keys(fieldErrors).length
  const fieldWord = errorCount === 1 ? 'field' : 'fields'

  return {
    message: `Validation failed on ${errorCount} ${fieldWord}`,
    fields: fieldErrors
  }
}

/**
 * Export type helpers for domain schemas
 */
export type PaginationParams = z.infer<typeof PaginationSchema>
export type DateRange = z.infer<typeof DateRangeSchema>

/**
 * Standard paginated response envelope for collection endpoints.
 * Use this for all new paginated API responses.
 */
export interface PaginatedResponse<T> {
  data: T[]
  total: number
  page: number
  limit: number
  hasMore: boolean
}
