/**
 * Instance Manager
 *
 * Manages workflow instance lifecycle: creation, updates, completion.
 */

import { getModels } from '../../models';
import type { CreateInstanceOptions } from './types';
import type { IAutomationInstance, InstanceStatus, INodeExecutionState } from '../../models/AutomationWorkflow';
import mongoose from 'mongoose';

// ============================================
// INSTANCE MANAGER CLASS
// ============================================

export class InstanceManager {
  /**
   * Create a new workflow instance
   */
  async createInstance(options: CreateInstanceOptions): Promise<IAutomationInstance> {
    const { AutomationWorkflow, AutomationInstance } = getModels();

    // Get workflow to find the trigger node (start point)
    const workflow = await AutomationWorkflow.findById(options.workflowId).lean();
    if (!workflow) {
      throw new Error(`Workflow ${options.workflowId} not found`);
    }

    // Find the starting node for the workflow.
    // The trigger exists both as workflow.trigger and as a node in workflow.nodes,
    // but they may have different IDs. We need to find the trigger node in the
    // nodes array (by type) and use its ID so the ExecutionEngine can find it.
    const triggerNode = workflow.nodes?.find(
      (n: any) => n.type === workflow.trigger?.type
    );

    // If found, start from the trigger node. Otherwise fall back to the
    // trigger.id (which may not match any node in the array).
    const currentNodeId = triggerNode?.id || workflow.trigger?.id || workflow.nodes?.[0]?.id;

    // Create instance
    const instance = await AutomationInstance.create({
      companyId: options.companyId,
      workflowId: new mongoose.Types.ObjectId(options.workflowId),
      workflowVersion: workflow.version,
      contactId: options.contactId,
      status: 'pending',
      currentNodeId,
      executedNodes: [],
      nodeStates: {},
      enteredAt: new Date(),
      variables: options.variables || {},
      ...(options.testMode && { variables: { ...options.variables, _testMode: true } }),
    });

    console.log(`[InstanceManager] Created instance ${instance._id} for workflow ${options.workflowId}`);

    return instance;
  }

  /**
   * Get instance by ID
   */
  async getInstance(instanceId: string): Promise<IAutomationInstance | null> {
    const { AutomationInstance } = getModels();
    return AutomationInstance.findById(instanceId).lean();
  }

  /**
   * Get all instances for a workflow
   */
  async getWorkflowInstances(
    workflowId: string,
    filters?: {
      status?: InstanceStatus;
      contactId?: string;
      limit?: number;
      offset?: number;
    }
  ): Promise<IAutomationInstance[]> {
    const { AutomationInstance } = getModels();

    const query: any = { workflowId: new mongoose.Types.ObjectId(workflowId) };

    if (filters?.status) {
      query.status = filters.status;
    }
    if (filters?.contactId) {
      query.contactId = filters.contactId;
    }

    let queryBuilder = AutomationInstance.find(query).sort({ createdAt: -1 });

    if (filters?.offset) {
      queryBuilder = queryBuilder.skip(filters.offset);
    }
    if (filters?.limit) {
      queryBuilder = queryBuilder.limit(filters.limit);
    }

    return queryBuilder.lean();
  }

  /**
   * Update instance status
   */
  async updateStatus(instanceId: string, status: InstanceStatus): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        status,
        lastProcessedAt: new Date(),
      },
    });
  }

  /**
   * Complete an instance
   */
  async completeInstance(
    instanceId: string,
    status: 'completed' | 'exited' = 'completed',
    exitReason?: string
  ): Promise<void> {
    const { AutomationInstance } = getModels();

    const update: any = {
      status,
      completedAt: new Date(),
      lastProcessedAt: new Date(),
    };

    if (exitReason) {
      update.exitReason = exitReason;
    }

    await AutomationInstance.findByIdAndUpdate(instanceId, { $set: update });

    // Update workflow stats
    await this.updateWorkflowStats(instanceId);

    console.log(`[InstanceManager] Completed instance ${instanceId} with status ${status}`);
  }

  /**
   * Mark instance as error
   */
  async markAsError(instanceId: string, nodeId: string, errorMessage: string): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        status: 'error',
        lastProcessedAt: new Date(),
      },
      $push: {
        errors: {
          nodeId,
          message: errorMessage,
          timestamp: new Date(),
          retryCount: 0,
        },
      },
    });

    console.log(`[InstanceManager] Marked instance ${instanceId} as error: ${errorMessage}`);
  }

  /**
   * Retry an instance (reset error state)
   */
  async retryInstance(instanceId: string): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        status: 'pending',
        lastProcessedAt: new Date(),
      },
      $pop: { errors: 1 }, // Remove last error
    });

    console.log(`[InstanceManager] Retrying instance ${instanceId}`);
  }

  /**
   * Pause an instance
   */
  async pauseInstance(instanceId: string): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        status: 'paused',
        lastProcessedAt: new Date(),
      },
    });

    console.log(`[InstanceManager] Paused instance ${instanceId}`);
  }

  /**
   * Resume a paused instance
   */
  async resumeInstance(instanceId: string): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        status: 'running',
        lastProcessedAt: new Date(),
      },
    });

    console.log(`[InstanceManager] Resumed instance ${instanceId}`);
  }

  /**
   * Update instance variables
   */
  async updateVariables(instanceId: string, variables: Record<string, any>): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        variables,
      },
    });
  }

  /**
   * Set node execution state
   */
  async setNodeState(
    instanceId: string,
    nodeId: string,
    state: INodeExecutionState
  ): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        [`nodeStates.${nodeId}`]: state,
      },
    });
  }

  /**
   * Check if contact is already in a workflow
   */
  async isContactInWorkflow(
    companyId: string,
    workflowId: string,
    contactId: string
  ): Promise<boolean> {
    const { AutomationInstance } = getModels();

    const count = await AutomationInstance.countDocuments({
      companyId,
      workflowId: new mongoose.Types.ObjectId(workflowId),
      contactId,
      status: { $in: ['pending', 'running', 'paused'] },
    });

    return count > 0;
  }

  /**
   * Get active instances count for a workflow
   */
  async getActiveCount(workflowId: string): Promise<number> {
    const { AutomationInstance } = getModels();

    return AutomationInstance.countDocuments({
      workflowId: new mongoose.Types.ObjectId(workflowId),
      status: { $in: ['pending', 'running', 'paused'] },
    });
  }

  /**
   * Get instances waiting for a specific event
   */
  async getWaitingForEvent(eventType: string): Promise<IAutomationInstance[]> {
    const { AutomationInstance } = getModels();

    return AutomationInstance.find({
      status: 'paused',
      waitingForEvent: eventType,
    }).lean();
  }

  /**
   * Clear waiting event from instance (when event occurs)
   */
  async clearWaitingEvent(instanceId: string): Promise<void> {
    const { AutomationInstance } = getModels();

    await AutomationInstance.findByIdAndUpdate(instanceId, {
      $set: {
        waitingForEvent: null,
        status: 'pending',
      },
    });
  }

  /**
   * Update workflow statistics after instance completion
   */
  private async updateWorkflowStats(instanceId: string): Promise<void> {
    const { AutomationInstance, AutomationWorkflow } = getModels();

    const instance = await AutomationInstance.findById(instanceId).lean();
    if (!instance) return;

    // Get all instance stats for this workflow
    const stats = await AutomationInstance.aggregate([
      { $match: { workflowId: instance.workflowId } },
      {
        $group: {
          _id: null,
          totalInstances: { $sum: 1 },
          activeInstances: {
            $sum: {
              $cond: [{ $in: ['$status', ['pending', 'running', 'paused']] }, 1, 0],
            },
          },
          completedInstances: {
            $sum: { $cond: [{ $eq: ['$status', 'completed'] }, 1, 0] },
          },
          exitedInstances: {
            $sum: { $cond: [{ $eq: ['$status', 'exited'] }, 1, 0] },
          },
          errorInstances: {
            $sum: { $cond: [{ $eq: ['$status', 'error'] }, 1, 0] },
          },
          avgCompletionTime: {
            $avg: {
              $cond: [
                { $eq: ['$status', 'completed'] },
                { $subtract: ['$completedAt', '$enteredAt'] },
                null,
              ],
            },
          },
          lastTriggeredAt: { $max: '$enteredAt' },
        },
      },
    ]);

    if (stats.length > 0) {
      const stat = stats[0];
      await AutomationWorkflow.findByIdAndUpdate(instance.workflowId, {
        $set: {
          stats: {
            totalInstances: stat.totalInstances,
            activeInstances: stat.activeInstances,
            completedInstances: stat.completedInstances,
            exitedInstances: stat.exitedInstances,
            errorInstances: stat.errorInstances,
            averageCompletionTime: stat.avgCompletionTime,
            lastTriggeredAt: stat.lastTriggeredAt,
          },
        },
      });
    }
  }
}