/**
 * Trigger Service
 *
 * Matches events to active workflows and creates instances.
 * Listens for events and triggers workflow execution.
 */

import { getModels } from '../../models';
import { InstanceManager } from './InstanceManager';
import type { TriggerEvent, TriggerMatch } from './types';
import type { IAutomationWorkflow } from '../../models/AutomationWorkflow';

// ============================================
// TRIGGER SERVICE CLASS
// ============================================

export class TriggerService {
  private instanceManager: InstanceManager;

  constructor() {
    this.instanceManager = new InstanceManager();
  }

  /**
   * Process a trigger event
   * Finds matching workflows and creates instances
   */
  async processTrigger(event: TriggerEvent): Promise<TriggerMatch[]> {
    const { AutomationWorkflow } = getModels();
    const matches: TriggerMatch[] = [];

    try {
      // Find all active workflows with matching trigger type
      const workflows = await AutomationWorkflow.find({
        companyId: event.companyId,
        status: 'active',
        'trigger.type': event.type,
        deletedAt: null,
      }).lean();

      console.log(`[TriggerService] processTrigger: type=${event.type}, companyId=${event.companyId}, found=${workflows.length} active workflows`);

      for (const workflow of workflows) {
        const filterResult = this.matchesFilters(workflow, event);
        console.log(`[TriggerService] Workflow "${workflow.name}" (${workflow._id}): filters=${filterResult}, trigger.config=`, JSON.stringify(workflow.trigger?.config));
      }

      // Process each workflow
      for (const workflow of workflows) {
        // Check if workflow matches event filters
        if (this.matchesFilters(workflow, event)) {
          // Check re-entry settings
          if (!workflow.settings?.allowReentry) {
            // Check if contact is already in this workflow
            const alreadyInWorkflow = await this.instanceManager.isContactInWorkflow(
              event.companyId,
              workflow._id.toString(),
              event.contact.id
            );

            if (alreadyInWorkflow) {
              continue; // Skip this workflow
            }
          }

          // Create instance
          const instance = await this.instanceManager.createInstance({
            companyId: event.companyId,
            workflowId: workflow._id.toString(),
            contactId: event.contact.id,
            variables: {
              ...event.data,
              trigger: event.data,
              contact: event.contact,
            },
          });

          // Immediately start execution (don't wait for worker poll)
          try {
            const { ExecutionEngine } = require('./ExecutionEngine');
            const engine = ExecutionEngine.getInstance();
            // Fire and forget — execution runs in background
            engine.processInstance(instance._id.toString()).catch((err: any) => {
              console.error(`[TriggerService] Error executing instance ${instance._id}:`, err.message);
            });
          } catch (err: any) {
            // If ExecutionEngine not available, instance will be picked up by worker poll
            console.warn('[TriggerService] Could not start execution immediately, instance will be processed by worker:', err.message);
          }

          matches.push({
            workflowId: workflow._id.toString(),
            triggerId: workflow.trigger.id,
            instanceId: instance._id.toString(),
          });
        }
      }

      return matches;
    } catch (error) {
      console.error('[TriggerService] Error processing trigger:', error);
      throw error;
    }
  }

  /**
   * Check if workflow trigger matches event filters and config
   */
  private matchesFilters(workflow: IAutomationWorkflow, event: TriggerEvent): boolean {
    const triggerConfig = workflow.trigger.config || {};
    const triggerFilters = workflow.trigger.filters;

    // Check trigger config constraints first.
    // If the trigger targets a specific list, the event MUST carry a matching
    // list id. Fail closed: a missing/renamed list id does NOT match every list
    // (previously an empty event listId skipped this check and over-sent to
    // additions on ANY list).
    if (triggerConfig.listId) {
      const configListId = String(triggerConfig.listId);
      const eventListIds = this.getEventListIds(event);
      if (!eventListIds.includes(configListId)) {
        return false; // List doesn't match (or event carried no list id) — skip.
      }
    }

    // If the trigger has a specific provider configured, check if the event came from that provider
    if (triggerConfig.provider && event.data?.provider) {
      if (String(triggerConfig.provider) !== String(event.data.provider)) {
        return false; // Provider doesn't match
      }
    }

    // No additional filters means match all (listId/provider already checked above)
    if (!triggerFilters || triggerFilters.length === 0) {
      return true;
    }

    // Check each filter
    for (const filter of triggerFilters) {
      const eventValue = this.getNestedValue(event.data, filter.field);
      const filterValue = filter.value;

      if (!this.evaluateOperator(eventValue, filter.operator, filterValue)) {
        return false;
      }
    }

    return true;
  }

  /**
   * Collect the list id(s) an event carries, supporting both the normalized
   * `listIds` array and the legacy single `listId`. Returns string ids.
   */
  private getEventListIds(event: TriggerEvent): string[] {
    const data: any = event.data || {};
    const raw = data.listIds != null ? data.listIds : data.listId;
    if (raw == null) return [];
    const arr = Array.isArray(raw) ? raw : [raw];
    return arr
      .map((v: any) => String(v))
      .filter((v: string) => v !== '' && v !== 'undefined' && v !== 'null');
  }

  /**
   * Evaluate a filter operator
   */
  private evaluateOperator(eventValue: any, operator: string, filterValue: any): boolean {
    switch (operator) {
      case 'equals':
        return eventValue === filterValue;
      case 'not_equals':
        return eventValue !== filterValue;
      case 'contains':
        return String(eventValue).toLowerCase().includes(String(filterValue).toLowerCase());
      case 'not_contains':
        return !String(eventValue).toLowerCase().includes(String(filterValue).toLowerCase());
      case 'starts_with':
        return String(eventValue).startsWith(String(filterValue));
      case 'ends_with':
        return String(eventValue).endsWith(String(filterValue));
      case 'greater_than':
        return Number(eventValue) > Number(filterValue);
      case 'less_than':
        return Number(eventValue) < Number(filterValue);
      case 'greater_or_equal':
        return Number(eventValue) >= Number(filterValue);
      case 'less_or_equal':
        return Number(eventValue) <= Number(filterValue);
      case 'is_empty':
        return eventValue === undefined || eventValue === null || eventValue === '';
      case 'is_not_empty':
        return eventValue !== undefined && eventValue !== null && eventValue !== '';
      case 'is_true':
        return Boolean(eventValue) === true;
      case 'is_false':
        return Boolean(eventValue) === false;
      default:
        return false;
    }
  }

  /**
   * Get nested value from object using dot notation
   */
  private getNestedValue(obj: Record<string, any>, path: string): any {
    return path.split('.').reduce((current, key) => current?.[key], obj);
  }

  // ==========================================
  // TRIGGER EVENT EMITTER METHODS
  // These methods are called by other services when events occur
  // ==========================================

  /**
   * Trigger: Contact Created / Subscribed
   */
  async onContactCreated(companyId: string, contact: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_subscribed',
      companyId,
      contact: this.extractContactData(contact),
      data: { contact },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Contact Updated
   */
  async onContactUpdated(companyId: string, contact: any, changes: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_subscribed',
      companyId,
      contact: this.extractContactData(contact),
      data: { contact, changes },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Contact Added to List
   */
  async onContactAddedToList(
    companyId: string,
    contact: any,
    listId: string | string[]
  ): Promise<TriggerMatch[]> {
    const listIds = (Array.isArray(listId) ? listId : [listId])
      .map((v) => (v == null ? '' : String(v)))
      .filter((v) => v !== '');
    return this.processTrigger({
      type: 'trigger_subscribed',
      companyId,
      contact: this.extractContactData(contact),
      // Carry both a primary listId (back-compat) and the full listIds array.
      data: { listId: listIds[0] || '', listIds },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Tag Added
   */
  async onTagAdded(companyId: string, contact: any, tagName: string): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_tag_added',
      companyId,
      contact: this.extractContactData(contact),
      data: { tagName },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Tag Removed
   */
  async onTagRemoved(companyId: string, contact: any, tagName: string): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_tag_removed',
      companyId,
      contact: this.extractContactData(contact),
      data: { tagName },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Form Submitted
   */
  async onFormSubmitted(companyId: string, contact: any, formData: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_form_submitted',
      companyId,
      contact: this.extractContactData(contact),
      data: { formId: formData.formId, formData },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Email Opened
   */
  async onEmailOpened(companyId: string, contact: any, emailData: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_email_opened',
      companyId,
      contact: this.extractContactData(contact),
      data: { emailId: emailData.emailId, campaignId: emailData.campaignId },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Email Clicked / Link Clicked
   */
  async onEmailClicked(companyId: string, contact: any, linkData: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_link_clicked',
      companyId,
      contact: this.extractContactData(contact),
      data: { linkUrl: linkData.url, emailId: linkData.emailId },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Order Created / Purchase Made
   */
  async onOrderCreated(companyId: string, contact: any, orderData: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_purchase_made',
      companyId,
      contact: this.extractContactData(contact),
      data: { orderId: orderData.id, amount: orderData.amount, items: orderData.items },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Cart Abandoned
   */
  async onCartAbandoned(companyId: string, contact: any, cartData: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_abandoned_cart',
      companyId,
      contact: this.extractContactData(contact),
      data: { cartId: cartData.id, items: cartData.items, total: cartData.total },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: API Call (webhook)
   */
  async onApiTrigger(companyId: string, triggerKey: string, payload: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_api_call',
      companyId,
      contact: this.extractContactData(payload.contact),
      data: { triggerKey, ...payload },
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Birthday
   */
  async onBirthday(companyId: string, contact: any): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_birthday',
      companyId,
      contact: this.extractContactData(contact),
      data: {},
      timestamp: new Date(),
    });
  }

  /**
   * Trigger: Anniversary
   */
  async onAnniversary(companyId: string, contact: any, years: number): Promise<TriggerMatch[]> {
    return this.processTrigger({
      type: 'trigger_anniversary',
      companyId,
      contact: this.extractContactData(contact),
      data: { years },
      timestamp: new Date(),
    });
  }

  /**
   * Extract contact data for execution context
   */
  private extractContactData(contact: any): any {
    if (!contact) {
      return { id: '', email: '', phone: '' };
    }

    return {
      id: contact._id?.toString() || contact.id || '',
      email: contact.email || '',
      phone: contact.phone || '',
      firstName: contact.firstName || '',
      lastName: contact.lastName || '',
      tags: contact.tags || [],
      customFields: contact.customFields || {},
      lists: contact.lists || [],
      segments: contact.segments || [],
    };
  }
}

// Singleton export
export const triggerService = new TriggerService();