/**
 * Event-Driven Workflow Schema
 * Defines strict types and validation rules for event-driven system architecture
 */

import { z } from 'zod';

/**
 * Event Definition Schema
 * Events are system-wide communication contracts
 */
const EventDefinitionSchema = z.object({
  description: z.string().min(1),
  payload: z.record(z.string(), z.enum(['string', 'number', 'boolean', 'array', 'object'])),
});

/**
 * Component Schema
 * Components are logical parts of the system that emit/consume events
 */
const ComponentSchema = z.object({
  description: z.string().min(1),
  emits: z.array(z.string()).default([]),
  consumes: z.array(z.string()).default([]),
});

/**
 * Actor Schema
 * Actors perform workflows (humans or systems)
 */
const ActorSchema = z.object({
  type: z.enum(['human', 'system']),
  name: z.string().min(1),
  description: z.string().min(1),
});

/**
 * Workflow Step Schema
 * Individual steps within a workflow
 */
const WorkflowStepSchema = z.object({
  id: z.string().min(1),
  action: z.string().min(1),
  component: z.string().min(1), // Must reference a defined component
  emits: z.string().optional(), // Must reference a defined event
  consumes: z.string().optional(), // Must reference a defined event
  depends_on: z.array(z.string()).optional(),
  choices: z.array(z.string()).optional(), // For branching (e.g., payment methods)
  on_success: z.string().optional(),
  on_failure: z.string().optional(),
});

/**
 * Workflow Schema
 * Step-by-step processes that actors perform
 */
const WorkflowSchema = z.object({
  actor: z.string().min(1), // Must reference a defined actor
  description: z.string().min(1),
  steps: z.array(WorkflowStepSchema).min(1),
});

/**
 * Service Configuration Schema
 * Concrete service implementations
 */
const ServiceConfigSchema = z.object({
  type: z.string().min(1), // e.g., payment_processor, oms, email_provider
  config: z.record(z.string(), z.any()).optional(),
  // Context-based usage (optional)
  use_for: z.array(z.string()).optional(), // e.g., ["signup", "admin_login"], or ["*"] for all contexts
  fallback: z.boolean().default(false), // Use as fallback if primary fails
});

/**
 * Stack Schema
 * Maps abstract components to concrete services
 */
const StackComponentSchema = z.object({
  services: z.record(z.string(), ServiceConfigSchema).refine((services) => Object.keys(services).length > 0, {
    message: "At least one service must be defined"
  }),
  primary: z.string().optional(), // Primary/default service to use (service name)
  routing: z.enum(['primary_only', 'context_based', 'load_balanced']).default('primary_only'),
});

/**
 * Action Schema
 * Specific tasks that can be performed
 */
const ActionSchema = z.object({
  description: z.string().min(1),
  component: z.string().min(1), // Must reference a defined component
  emits: z.string().optional(), // Must reference a defined event
  consumes: z.string().optional(), // Must reference a defined event
  inputs: z.array(z.record(z.string(), z.string())).optional(),
});

/**
 * Service Configuration Schema
 * For configuring additional infrastructure services
 */
const ServiceInstanceSchema = z.object({
  provider: z.string().min(1), // e.g., "redis", "elasticache", "upstash"
  connection: z.string().min(1), // Connection string or config
  config: z.record(z.string(), z.any()).optional(),
});

/**
 * Worker Configuration Schema
 * For background job processing
 */
const WorkerConfigSchema = z.object({
  enabled: z.boolean().default(true),
  concurrency: z.number().default(1),
  queues: z.array(z.string()).default(['default']),
  config: z.record(z.string(), z.any()).optional(),
});

/**
 * Stage Infrastructure Schema
 * Multi-domain, multi-database, multi-service infrastructure
 */
const StageInfrastructureSchema = z.object({
  // Multiple domains (web app, API, docs, admin panel, etc.)
  domains: z.object({
    web: z.string().optional(), // Main web application
    api: z.string().optional(), // API endpoint
    docs: z.string().optional(), // Documentation site
    portal: z.string().optional(), // Customer/partner portal
    admin: z.string().optional(), // Admin panel
    marketing: z.string().optional(), // Marketing site
    custom: z.record(z.string(), z.string()).optional(), // Additional custom domains
  }).optional(),

  // Multiple databases (primary, analytics, cache, etc.)
  databases: z.object({
    primary: z.string(), // Main application database (REQUIRED)
    analytics: z.string().optional(), // Analytics/reporting database
    cache: z.string().optional(), // Cache database (Redis, etc.)
    custom: z.record(z.string(), z.string()).optional(), // Additional databases
  }),

  // Additional infrastructure services
  services: z.object({
    redis: ServiceInstanceSchema.optional(), // Cache, session store
    queue: ServiceInstanceSchema.optional(), // Job queue (Bull, BullMQ, etc.)
    storage: ServiceInstanceSchema.optional(), // Object storage (S3, R2, etc.)
    search: ServiceInstanceSchema.optional(), // Search engine (Elasticsearch, Algolia, etc.)
    email: ServiceInstanceSchema.optional(), // Email service (SendGrid, SES, etc.)
    cdn: ServiceInstanceSchema.optional(), // CDN (CloudFront, Cloudflare, etc.)
    monitoring: ServiceInstanceSchema.optional(), // Monitoring (DataDog, Sentry, etc.)
    custom: z.record(z.string(), ServiceInstanceSchema).optional(), // Additional services
  }).optional(),

  // Background workers
  workers: WorkerConfigSchema.optional(),
});

/**
 * Stage Configuration Schema
 * Smart defaults with override capability
 */
const StageConfigSchema = z.object({
  log_level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
  development_tools: z.boolean().optional(), // Override development tools enablement
  error_details: z.boolean().default(false), // Show detailed errors (default false for security)
  cors: z.object({
    enabled: z.boolean().default(true),
    origins: z.array(z.string()).optional(), // Allowed origins
  }).optional(),
  rate_limiting: z.object({
    enabled: z.boolean().default(true),
    requests_per_minute: z.number().default(100),
  }).optional(),
  custom: z.record(z.string(), z.any()).optional(), // Additional custom config
});

/**
 * Stage Schema
 * Environment configurations with full infrastructure support
 */
const StageSchema = z.object({
  description: z.string().optional(),
  infrastructure: StageInfrastructureSchema,
  config: StageConfigSchema.optional(),
  env: z.record(z.string(), z.string()).optional(), // Environment variables
});

/**
 * Main Event-Driven Workflow Schema
 */
export const EventDrivenWorkflowSchema = z.object({
  workflow: z.object({
    id: z.string().min(1),
    version: z.string().min(1),
    name: z.string().min(1),
    description: z.string().min(1),
  }),

  // Events: System-wide event definitions (must be defined before use)
  events: z.record(z.string(), EventDefinitionSchema),

  // Actors: Who/what performs workflows
  actors: z.record(z.string(), ActorSchema),

  // Components: Logical parts that emit/consume events
  components: z.record(z.string(), ComponentSchema),

  // Workflows: Step-by-step processes
  workflows: z.record(z.string(), WorkflowSchema),

  // Stack: Maps components to services
  stack: z.record(z.string(), StackComponentSchema),

  // Actions: Reusable tasks (optional)
  actions: z.record(z.string(), ActionSchema).optional(),

  // Stages: Environment configs (optional)
  stages: z.record(z.string(), StageSchema).optional(),
}).superRefine((data, ctx) => {
  // Custom validation: Ensure referential integrity

  // Collect all defined event names
  const definedEvents = new Set(Object.keys(data.events));

  // Collect all defined component names
  const definedComponents = new Set(Object.keys(data.components));

  // Collect all defined actor names
  const definedActors = new Set(Object.keys(data.actors));

  // Validate components emit/consume only defined events
  Object.entries(data.components).forEach(([componentName, component]) => {
    component.emits.forEach((event) => {
      if (!definedEvents.has(event)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Component '${componentName}' emits undefined event '${event}'. Event must be defined in 'events' section.`,
          path: ['components', componentName, 'emits'],
        });
      }
    });

    component.consumes.forEach((event) => {
      if (!definedEvents.has(event)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Component '${componentName}' consumes undefined event '${event}'. Event must be defined in 'events' section.`,
          path: ['components', componentName, 'consumes'],
        });
      }
    });
  });

  // Validate workflows reference defined actors and components
  Object.entries(data.workflows).forEach(([workflowName, workflow]) => {
    if (!definedActors.has(workflow.actor)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: `Workflow '${workflowName}' references undefined actor '${workflow.actor}'. Actor must be defined in 'actors' section.`,
        path: ['workflows', workflowName, 'actor'],
      });
    }

    workflow.steps.forEach((step, index) => {
      if (!definedComponents.has(step.component)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Workflow '${workflowName}' step '${step.id}' references undefined component '${step.component}'. Component must be defined in 'components' section.`,
          path: ['workflows', workflowName, 'steps', index, 'component'],
        });
      }

      if (step.emits && !definedEvents.has(step.emits)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Workflow '${workflowName}' step '${step.id}' emits undefined event '${step.emits}'. Event must be defined in 'events' section.`,
          path: ['workflows', workflowName, 'steps', index, 'emits'],
        });
      }

      if (step.consumes && !definedEvents.has(step.consumes)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Workflow '${workflowName}' step '${step.id}' consumes undefined event '${step.consumes}'. Event must be defined in 'events' section.`,
          path: ['workflows', workflowName, 'steps', index, 'consumes'],
        });
      }
    });
  });

  // Validate stack maps to defined components
  Object.keys(data.stack).forEach((componentName) => {
    if (!definedComponents.has(componentName)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: `Stack defines services for undefined component '${componentName}'. Component must be defined in 'components' section.`,
        path: ['stack', componentName],
      });
    }
  });

  // Validate actions reference defined components and events
  if (data.actions) {
    Object.entries(data.actions).forEach(([actionName, action]) => {
      if (!definedComponents.has(action.component)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Action '${actionName}' references undefined component '${action.component}'. Component must be defined in 'components' section.`,
          path: ['actions', actionName, 'component'],
        });
      }

      if (action.emits && !definedEvents.has(action.emits)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Action '${actionName}' emits undefined event '${action.emits}'. Event must be defined in 'events' section.`,
          path: ['actions', actionName, 'emits'],
        });
      }

      if (action.consumes && !definedEvents.has(action.consumes)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `Action '${actionName}' consumes undefined event '${action.consumes}'. Event must be defined in 'events' section.`,
          path: ['actions', actionName, 'consumes'],
        });
      }
    });
  }

  // Validate event flow: ensure emitted events are consumed somewhere
  const emittedEvents = new Set<string>();
  const consumedEvents = new Set<string>();

  Object.values(data.components).forEach((component) => {
    component.emits.forEach((event) => emittedEvents.add(event));
    component.consumes.forEach((event) => consumedEvents.add(event));
  });

  // Warn if events are emitted but never consumed
  emittedEvents.forEach((event) => {
    if (!consumedEvents.has(event)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: `Event '${event}' is emitted but never consumed by any component. This may indicate a missing consumer.`,
        path: ['events', event],
      });
    }
  });

  // Warn if events are consumed but never emitted
  consumedEvents.forEach((event) => {
    if (!emittedEvents.has(event)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: `Event '${event}' is consumed but never emitted by any component. This may indicate a missing emitter.`,
        path: ['events', event],
      });
    }
  });
});

export type EventDrivenWorkflow = z.infer<typeof EventDrivenWorkflowSchema>;
