/**
 * Node Registry
 *
 * Registry of all node type handlers for workflow execution.
 * Each handler defines validation, execution, and metadata for its node type.
 */

import mongoose from 'mongoose';
import type { NodeHandler, NodeExecutionResult, NodeMetadata, ExecutionContext } from './types';
import type { IAutomationInstance, IWorkflowNode, INodeConfig } from '../../models/AutomationWorkflow';
import { enqueueEmailDispatch } from '../email/emailDispatchWorker';
import { getModels } from '../../models';
import { renderTemplateToHtml } from './EmailRendererService';

// ============================================
// NODE REGISTRY CLASS
// ============================================

export class NodeRegistry {
  private handlers: Map<string, NodeHandler> = new Map();

  constructor() {
    // Register built-in handlers
    this.registerBuiltInHandlers();
  }

  /**
   * Register a node handler
   */
  register(handler: NodeHandler): void {
    if (this.handlers.has(handler.type)) {
      console.warn(`[NodeRegistry] Handler for "${handler.type}" already registered. Overwriting.`);
    }
    this.handlers.set(handler.type, handler);
  }

  /**
   * Get a handler by node type
   */
  get(type: string): NodeHandler | undefined {
    return this.handlers.get(type);
  }

  /**
   * Get all registered handlers
   */
  getAll(): NodeHandler[] {
    return Array.from(this.handlers.values());
  }

  /**
   * Get handlers by category
   */
  getByCategory(category: 'trigger' | 'action' | 'flow'): NodeHandler[] {
    return this.getAll().filter((h) => h.category === category);
  }

  /**
   * Check if a node type is registered
   */
  has(type: string): boolean {
    return this.handlers.has(type);
  }

  /**
   * Register all built-in handlers
   */
  private registerBuiltInHandlers(): void {
    // Register action handlers
    this.register(SendEmailHandler);
    this.register(SendSmsHandler);
    this.register(SendWhatsAppHandler);
    this.register(AddTagHandler);
    this.register(RemoveTagHandler);
    this.register(UpdateContactHandler);
    this.register(UpdateCrmFieldHandler);
    this.register(CreateCrmTaskHandler);
    this.register(CallWebhookHandler);
    this.register(SendNotificationHandler);
    this.register(AiGenerateHandler);

    // Register flow control handlers
    this.register(DelayHandler);
    this.register(WaitUntilDateHandler);
    this.register(WaitUntilTimeHandler);
    this.register(WaitUntilEventHandler);
    this.register(ConditionHandler);
    this.register(SplitHandler);
    this.register(GoalHandler);
    this.register(ExitHandler);
    this.register(EndHandler);
  }
}

// ============================================
// ACTION HANDLERS
// ============================================

/**
 * Send Email Action
 */
const SendEmailHandler: NodeHandler = {
  type: 'action_send_email',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.internalTemplateId && !config.emailTemplateId && !config.emailSubject && !config.emailHtmlContent) {
      errors.push('Email template, subject, or HTML content is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const config = node.config;

      // In test mode, don't actually send
      if (context.isTestMode) {
        return {
          success: true,
          output: {
            action: 'send_email',
            internalTemplateId: config.internalTemplateId,
            templateId: config.emailTemplateId,
            recipient: context.contact.email,
            testMode: true,
          },
        };
      }

      // Resolve provider from node config or workflow settings
      const provider = config.emailProvider || (context.settings as any)?.platform || undefined;

      // Resolve recipient email
      const recipientEmail = context.contact.email || (context.contact as any)?.EmailAddress || '';
      if (!recipientEmail) {
        return {
          success: false,
          error: 'No email address found for contact',
        };
      }

      // Resolve template content
      let htmlContent = config.emailHtmlContent;
      let subject = config.emailSubject || '';
      let senderName = config.senderName || config.emailFromName || '';
      let replyTo = config.emailReplyTo || '';

      // If an internal template is specified, resolve it
      if (config.internalTemplateId) {
        try {
          const { EmailDesignerTemplate } = getModels();
          let template = null;

          // Only use findById for valid MongoDB ObjectIds — local IDs like
          // "emailDesignerTemplate-xxx" would throw a CastError
          const isObjectId = mongoose.Types.ObjectId.isValid(config.internalTemplateId)
            && new mongoose.Types.ObjectId(config.internalTemplateId).toString() === config.internalTemplateId;

          if (isObjectId) {
            template = await EmailDesignerTemplate.findById(config.internalTemplateId);
          }

          // Fallback: try looking up by slug or name within the company
          // This handles local dataStore IDs (e.g. "emailDesignerTemplate-xxx")
          // that were saved before the backendId sync was in place
          if (!template) {
            template = await EmailDesignerTemplate.findOne({
              companyId: context.companyId,
              $or: [
                { slug: config.internalTemplateId },
                { name: config.internalTemplateId },
              ],
            });
          }

          if (template) {
            // Render blocks to HTML if htmlOutput is empty
            const renderedHtml = renderTemplateToHtml(template);
            htmlContent = renderedHtml;

            // Use template's subject/sender/replyTo as defaults if not overridden in config
            if (!subject && template.subject) {
              subject = template.subject;
            }
            if (!senderName && template.senderName) {
              senderName = template.senderName;
            }
            if (!replyTo && template.replyToEmail) {
              replyTo = template.replyToEmail;
            }
          } else {
            console.warn(`[SendEmailHandler] Internal template "${config.internalTemplateId}" not found by ID, slug, or name. Falling back to config values.`);
          }
        } catch (tmplErr: any) {
          console.error('[SendEmailHandler] Error resolving internal template:', tmplErr.message);
          // Fall through to config values
        }
      }

      // Enqueue on the durable dispatch queue instead of sending inline.
      // The email is sent (and retried on transient failure) by the
      // emailDispatchWorker; enqueue is idempotent per instance+node so a
      // node re-run can't double-send.
      const dispatch = await enqueueEmailDispatch({
        companyId: context.companyId,
        workflowId: instance.workflowId?.toString(),
        instanceId: instance._id?.toString(),
        nodeId: node.id,
        contactId: context.contact?.id || instance.contactId,
        provider,
        to: recipientEmail,
        subject,
        htmlContent,
        htmlUrl: config.emailHtmlUrl,
        templateId: config.emailTemplateId ? parseInt(config.emailTemplateId, 10) : undefined,
        senderId: config.senderId,
        senderEmail: config.senderEmail,
        senderName,
        replyTo,
        listIds: config.contactListIds?.map(id => parseInt(id, 10)).filter(n => !isNaN(n)),
      });

      return {
        success: true,
        output: {
          action: 'send_email',
          recipient: context.contact.email,
          dispatchId: dispatch?._id?.toString(),
          status: dispatch?.status || 'queued',
          provider: provider || 'default',
          queuedAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to send email',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Send Email',
    description: 'Send an email to the contact',
    icon: 'Mail',
    category: 'action',
  }),
};

/**
 * Send SMS Action
 */
const SendSmsHandler: NodeHandler = {
  type: 'action_send_sms',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.smsTemplate) {
      errors.push('SMS message is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { smsTemplate } = node.config;

      // Replace template variables
      let message = smsTemplate || '';
      Object.entries(context.variables).forEach(([key, value]) => {
        message = message.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
      });

      if (context.isTestMode) {
        return {
          success: true,
          output: {
            action: 'send_sms',
            message,
            recipient: context.contact.phone,
            testMode: true,
          },
        };
      }

      // TODO: Integrate with SMS provider (Twilio, Brevo SMS, etc.)
      console.log(`[Automation] Send SMS: to=${context.contact.phone} message=${message.substring(0, 50)}...`);

      return {
        success: true,
        output: {
          action: 'send_sms',
          message,
          recipient: context.contact.phone,
          sentAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to send SMS',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Send SMS',
    description: 'Send an SMS message to the contact',
    icon: 'MessageSquare',
    category: 'action',
  }),
};

/**
 * Send WhatsApp Action
 */
const SendWhatsAppHandler: NodeHandler = {
  type: 'action_send_whatsapp',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.whatsappTemplate) {
      errors.push('WhatsApp template is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { whatsappTemplate, whatsappTemplateParams } = node.config;

      if (context.isTestMode) {
        return {
          success: true,
          output: {
            action: 'send_whatsapp',
            template: whatsappTemplate,
            recipient: context.contact.phone,
            testMode: true,
          },
        };
      }

      // TODO: Integrate with WhatsApp Business API (Twilio, Meta, etc.)
      console.log(`[Automation] Send WhatsApp: to=${context.contact.phone}`);

      return {
        success: true,
        output: {
          action: 'send_whatsapp',
          template: whatsappTemplate,
          params: whatsappTemplateParams,
          recipient: context.contact.phone,
          sentAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to send WhatsApp message',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Send WhatsApp',
    description: 'Send a WhatsApp message to the contact',
    icon: 'MessageCircle',
    category: 'action',
  }),
};

/**
 * Add Tag Action
 */
const AddTagHandler: NodeHandler = {
  type: 'action_add_tag',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.tagName) {
      errors.push('Tag name is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { tagName } = node.config;

      // Update contact tags
      // TODO: Integrate with Contact model
      console.log(`[Automation] Add Tag: ${tagName} to contact ${context.contact.id}`);

      return {
        success: true,
        output: {
          action: 'add_tag',
          tag: tagName,
          contactId: context.contact.id,
          addedAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to add tag',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Add Tag',
    description: 'Add a tag to the contact',
    icon: 'Tag',
    category: 'action',
  }),
};

/**
 * Remove Tag Action
 */
const RemoveTagHandler: NodeHandler = {
  type: 'action_remove_tag',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.tagName) {
      errors.push('Tag name is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { tagName } = node.config;

      console.log(`[Automation] Remove Tag: ${tagName} from contact ${context.contact.id}`);

      return {
        success: true,
        output: {
          action: 'remove_tag',
          tag: tagName,
          contactId: context.contact.id,
          removedAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to remove tag',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Remove Tag',
    description: 'Remove a tag from the contact',
    icon: 'TagOff',
    category: 'action',
  }),
};

/**
 * Update Contact Action
 */
const UpdateContactHandler: NodeHandler = {
  type: 'action_update_contact',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.contactFields || Object.keys(config.contactFields || {}).length === 0) {
      errors.push('At least one field to update is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { contactFields } = node.config;

      // TODO: Integrate with Contact model
      console.log(`[Automation] Update Contact: ${context.contact.id}`, contactFields);

      return {
        success: true,
        output: {
          action: 'update_contact',
          contactId: context.contact.id,
          fields: contactFields,
          updatedAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to update contact',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Update Contact',
    description: 'Update contact fields',
    icon: 'UserCog',
    category: 'action',
  }),
};

/**
 * Update CRM Field Action
 */
const UpdateCrmFieldHandler: NodeHandler = {
  type: 'action_update_crm_field',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    // CRM field updates would need field name and value
    return { valid: true, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      console.log(`[Automation] Update CRM Field for contact ${context.contact.id}`);

      return {
        success: true,
        output: {
          action: 'update_crm_field',
          contactId: context.contact.id,
          updatedAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to update CRM field',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Update CRM Field',
    description: 'Update a CRM field value',
    icon: 'Database',
    category: 'action',
  }),
};

/**
 * Create CRM Task Action
 */
const CreateCrmTaskHandler: NodeHandler = {
  type: 'action_create_crm_task',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.crmTaskTitle) {
      errors.push('Task title is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { crmTaskType, crmTaskTitle, crmTaskDueDate, crmTaskAssignTo } = node.config;

      console.log(`[Automation] Create CRM Task: ${crmTaskTitle}`);

      return {
        success: true,
        output: {
          action: 'create_crm_task',
          taskType: crmTaskType,
          title: crmTaskTitle,
          dueDate: crmTaskDueDate,
          assignedTo: crmTaskAssignTo,
          createdAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to create CRM task',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Create CRM Task',
    description: 'Create a CRM follow-up task',
    icon: 'ListTodo',
    category: 'action',
  }),
};

/**
 * Call Webhook Action
 */
const CallWebhookHandler: NodeHandler = {
  type: 'action_call_webhook',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.webhookUrl) {
      errors.push('Webhook URL is required');
    }
    try {
      new URL(config.webhookUrl || '');
    } catch {
      errors.push('Invalid webhook URL');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { webhookUrl, webhookMethod = 'POST', webhookHeaders, webhookBody } = node.config;

      // Prepare payload with contact data
      const payload = {
        ...(webhookBody || {}),
        contact: context.contact,
        instanceId: instance._id,
        workflowId: instance.workflowId,
        variables: context.variables,
      };

      if (context.isTestMode) {
        return {
          success: true,
          output: {
            action: 'call_webhook',
            url: webhookUrl,
            method: webhookMethod,
            testMode: true,
          },
        };
      }

      // Make the webhook call
      const response = await fetch(webhookUrl!, {
        method: webhookMethod,
        headers: {
          'Content-Type': 'application/json',
          ...(webhookHeaders || {}),
        },
        body: webhookMethod !== 'GET' ? JSON.stringify(payload) : undefined,
      });

      if (!response.ok) {
        throw new Error(`Webhook failed with status ${response.status}`);
      }

      const responseData = await response.json().catch(() => ({}));

      return {
        success: true,
        output: {
          action: 'call_webhook',
          url: webhookUrl,
          method: webhookMethod,
          statusCode: response.status,
          response: responseData,
          calledAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to call webhook',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Call Webhook',
    description: 'Send data to an external webhook',
    icon: 'Webhook',
    category: 'action',
  }),
};

/**
 * Send Notification Action
 */
const SendNotificationHandler: NodeHandler = {
  type: 'action_send_notification',
  category: 'action',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.notificationMessage) {
      errors.push('Notification message is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      const { notificationType = 'info', notificationMessage, notificationRecipients } = node.config;

      console.log(`[Automation] Send Notification: ${notificationType} - ${notificationMessage}`);

      // TODO: Create notification in database
      return {
        success: true,
        output: {
          action: 'send_notification',
          type: notificationType,
          message: notificationMessage,
          recipients: notificationRecipients,
          sentAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to send notification',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Send Notification',
    description: 'Send an internal notification',
    icon: 'Bell',
    category: 'action',
  }),
};

/**
 * AI Generate Action
 */
const AiGenerateHandler: NodeHandler = {
  type: 'action_ai_generate',
  category: 'action',

  validate: async (config: INodeConfig) => {
    return { valid: true, errors: [] };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    try {
      // TODO: Integrate with AI service
      console.log(`[Automation] AI Generate for contact ${context.contact.id}`);

      return {
        success: true,
        output: {
          action: 'ai_generate',
          generatedAt: new Date().toISOString(),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message || 'Failed to generate content',
      };
    }
  },

  getMetadata: (): NodeMetadata => ({
    label: 'AI Generate',
    description: 'Generate content using AI',
    icon: 'Sparkles',
    category: 'action',
  }),
};

// ============================================
// FLOW CONTROL HANDLERS
// ============================================

/**
 * Delay Handler
 */
const DelayHandler: NodeHandler = {
  type: 'flow_delay',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.delayDuration || config.delayDuration < 1) {
      errors.push('Delay duration must be at least 1');
    }
    if (!config.delayUnit) {
      errors.push('Delay unit is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { delayDuration = 1, delayUnit = 'hours' } = node.config;

    // Calculate resume time
    const now = new Date();
    let resumeAt = new Date(now);

    switch (delayUnit) {
      case 'minutes':
        resumeAt.setMinutes(resumeAt.getMinutes() + delayDuration);
        break;
      case 'hours':
        resumeAt.setHours(resumeAt.getHours() + delayDuration);
        break;
      case 'days':
        resumeAt.setDate(resumeAt.getDate() + delayDuration);
        break;
      case 'weeks':
        resumeAt.setDate(resumeAt.getDate() + delayDuration * 7);
        break;
    }

    return {
      success: true,
      resumeAt,
      output: {
        action: 'delay',
        duration: delayDuration,
        unit: delayUnit,
        resumeAt: resumeAt.toISOString(),
      },
    };
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Delay',
    description: 'Wait for a specified duration',
    icon: 'Clock',
    category: 'flow',
  }),
};

/**
 * Wait Until Date Handler
 */
const WaitUntilDateHandler: NodeHandler = {
  type: 'flow_wait_until_date',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.waitDateField) {
      errors.push('Date field is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { waitDateField, waitTime } = node.config;

    // Get the date from contact or variables
    const dateValue = context.variables[waitDateField!] || context.contact[waitDateField!];

    if (!dateValue) {
      return {
        success: false,
        error: `Date field "${waitDateField}" not found`,
      };
    }

    let resumeAt = new Date(dateValue);

    // Add time if specified
    if (waitTime) {
      const [hours, minutes] = waitTime.split(':').map(Number);
      resumeAt.setHours(hours, minutes, 0, 0);
    }

    // If the date is in the past, skip
    if (resumeAt <= new Date()) {
      return {
        success: true,
        output: {
          action: 'wait_until_date',
          skipped: true,
          reason: 'Date is in the past',
        },
      };
    }

    return {
      success: true,
      resumeAt,
      output: {
        action: 'wait_until_date',
        dateField: waitDateField,
        resumeAt: resumeAt.toISOString(),
      },
    };
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Wait Until Date',
    description: 'Wait until a specific date',
    icon: 'CalendarClock',
    category: 'flow',
  }),
};

/**
 * Wait Until Time Handler
 */
const WaitUntilTimeHandler: NodeHandler = {
  type: 'flow_wait_until_time',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.waitTime) {
      errors.push('Time is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { waitTime } = node.config;

    const [hours, minutes] = waitTime!.split(':').map(Number);

    const now = new Date();
    const resumeAt = new Date();
    resumeAt.setHours(hours, minutes, 0, 0);

    // If the time has already passed today, schedule for tomorrow
    if (resumeAt <= now) {
      resumeAt.setDate(resumeAt.getDate() + 1);
    }

    return {
      success: true,
      resumeAt,
      output: {
        action: 'wait_until_time',
        time: waitTime,
        resumeAt: resumeAt.toISOString(),
      },
    };
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Wait Until Time',
    description: 'Wait until a specific time',
    icon: 'Timer',
    category: 'flow',
  }),
};

/**
 * Wait Until Event Handler
 */
const WaitUntilEventHandler: NodeHandler = {
  type: 'flow_wait_until_event',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.waitEventTrigger) {
      errors.push('Event trigger is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { waitEventTrigger, waitTimeout } = node.config;

    // Set waiting state - instance will be resumed when event occurs
    const timeoutMs = (waitTimeout || 24) * 60 * 60 * 1000; // Default 24 hours
    const resumeAt = new Date(Date.now() + timeoutMs);

    return {
      success: true,
      resumeAt: waitTimeout ? resumeAt : undefined,
      output: {
        action: 'wait_until_event',
        eventTrigger: waitEventTrigger,
        timeout: waitTimeout,
        waitingForEvent: waitEventTrigger,
      },
    };
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Wait Until Event',
    description: 'Wait for a specific event to occur',
    icon: 'Zap',
    category: 'flow',
  }),
};

/**
 * Condition Handler (If/Else)
 */
const ConditionHandler: NodeHandler = {
  type: 'flow_condition',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    if (!config.conditions || config.conditions.length === 0) {
      errors.push('At least one condition is required');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { conditions, conditionLogic = 'all' } = node.config;

    // Evaluate condition groups
    const results = conditions?.map((group) => {
      const groupResults = group.conditions.map((condition) => {
        return evaluateCondition(condition, context);
      });

      // Group logic
      if (group.logic === 'all') {
        return groupResults.every((r) => r);
      } else {
        return groupResults.some((r) => r);
      }
    }) || [];

    // Overall logic
    let finalResult: boolean;
    if (conditionLogic === 'all') {
      finalResult = results.every((r) => r);
    } else {
      finalResult = results.some((r) => r);
    }

    return {
      success: true,
      branch: finalResult ? 'true' : 'false',
      output: {
        action: 'condition',
        result: finalResult,
        evaluatedAt: new Date().toISOString(),
      },
    };
  },

  getOutputs: (config: INodeConfig): string[] => ['true', 'false'],

  getMetadata: (): NodeMetadata => ({
    label: 'If/Else',
    description: 'Branch based on conditions',
    icon: 'GitBranch',
    category: 'flow',
  }),
};

/**
 * Split Handler (A/B Testing)
 */
const SplitHandler: NodeHandler = {
  type: 'flow_split',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    const errors: string[] = [];
    const percentage = config.splitPercentage || 50;
    if (percentage < 1 || percentage > 99) {
      errors.push('Split percentage must be between 1 and 99');
    }
    return { valid: errors.length === 0, errors };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { splitPercentage = 50 } = node.config;

    // Use consistent hashing based on contact ID for deterministic routing
    const hash = hashString(context.contact.id);
    const path = (hash % 100) < splitPercentage ? 'true' : 'false';

    return {
      success: true,
      branch: path,
      selectedPath: path,
      output: {
        action: 'split',
        percentage: splitPercentage,
        selectedPath: path,
        contactId: context.contact.id,
      },
    };
  },

  getOutputs: (config: INodeConfig): string[] => ['true', 'false'],

  getMetadata: (): NodeMetadata => ({
    label: 'Split',
    description: 'A/B split traffic',
    icon: 'Split',
    category: 'flow',
  }),
};

/**
 * Goal Handler
 */
const GoalHandler: NodeHandler = {
  type: 'flow_goal',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    return { valid: true, errors: [] };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { goalType, goalCriteria } = node.config;

    // Mark goal as reached
    return {
      success: true,
      output: {
        action: 'goal',
        goalType,
        goalCriteria,
        reachedAt: new Date().toISOString(),
      },
    };
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Goal',
    description: 'Mark a goal as achieved',
    icon: 'Target',
    category: 'flow',
  }),
};

/**
 * Exit Handler
 */
const ExitHandler: NodeHandler = {
  type: 'flow_exit',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    return { valid: true, errors: [] };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    const { exitReason } = node.config;

    return {
      success: true,
      output: {
        action: 'exit',
        reason: exitReason,
        exitedAt: new Date().toISOString(),
      },
    };
  },

  getMetadata: (): NodeMetadata => ({
    label: 'Exit',
    description: 'Exit the workflow early',
    icon: 'LogOut',
    category: 'flow',
  }),
};

/**
 * End Handler
 */
const EndHandler: NodeHandler = {
  type: 'flow_end',
  category: 'flow',

  validate: async (config: INodeConfig) => {
    return { valid: true, errors: [] };
  },

  execute: async (
    instance: IAutomationInstance,
    node: IWorkflowNode,
    context: ExecutionContext
  ): Promise<NodeExecutionResult> => {
    return {
      success: true,
      output: {
        action: 'end',
        completedAt: new Date().toISOString(),
      },
    };
  },

  getMetadata: (): NodeMetadata => ({
    label: 'End',
    description: 'End of workflow',
    icon: 'CircleStop',
    category: 'flow',
  }),
};

// ============================================
// HELPER FUNCTIONS
// ============================================

/**
 * Evaluate a single condition
 */
function evaluateCondition(
  condition: { field: string; operator: string; value: any },
  context: ExecutionContext
): boolean {
  const fieldValue = getNestedValue(context.variables, condition.field) ??
    getNestedValue(context.contact, condition.field);

  switch (condition.operator) {
    case 'equals':
      return fieldValue === condition.value;
    case 'not_equals':
      return fieldValue !== condition.value;
    case 'contains':
      return String(fieldValue).toLowerCase().includes(String(condition.value).toLowerCase());
    case 'not_contains':
      return !String(fieldValue).toLowerCase().includes(String(condition.value).toLowerCase());
    case 'starts_with':
      return String(fieldValue).startsWith(String(condition.value));
    case 'ends_with':
      return String(fieldValue).endsWith(String(condition.value));
    case 'greater_than':
      return Number(fieldValue) > Number(condition.value);
    case 'less_than':
      return Number(fieldValue) < Number(condition.value);
    case 'greater_or_equal':
      return Number(fieldValue) >= Number(condition.value);
    case 'less_or_equal':
      return Number(fieldValue) <= Number(condition.value);
    case 'is_empty':
      return fieldValue === undefined || fieldValue === null || fieldValue === '';
    case 'is_not_empty':
      return fieldValue !== undefined && fieldValue !== null && fieldValue !== '';
    case 'is_true':
      return Boolean(fieldValue) === true;
    case 'is_false':
      return Boolean(fieldValue) === false;
    case 'before':
      return new Date(fieldValue) < new Date(condition.value);
    case 'after':
      return new Date(fieldValue) > new Date(condition.value);
    case 'between':
      const dateValue = new Date(fieldValue);
      return dateValue >= new Date(condition.value[0]) && dateValue <= new Date(condition.value[1]);
    default:
      return false;
  }
}

/**
 * Get nested value from object using dot notation
 */
function getNestedValue(obj: Record<string, any>, path: string): any {
  return path.split('.').reduce((current, key) => current?.[key], obj);
}

/**
 * Simple string hash for deterministic routing
 */
function hashString(str: string): number {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return Math.abs(hash);
}

// ============================================
// SINGLETON EXPORT
// ============================================

export const nodeRegistry = new NodeRegistry();