/**
 * Workflow Engine
 * Orchestrates the full ticket triage pipeline:
 *   1. Check if enabled
 *   2. Run exclusion filter rules
 *   3. Run robotic classification
 *   4. Validate classification
 *   5. If needed → AI enhancement
 *   6. Re-validate after AI
 *   7. Delay (configurable)
 *   8. Write back to Autotask
 *   9. Create notes if applicable
 *   10. Log full execution
 */

import { postgresClient } from './postgres-client';
import { AutotaskClient } from './autotask-client';
import { roboticClassifier } from './robotic-classifier';
import { triageValidator } from './triage-validator';
import { aiTriageService } from './ai-triage-service';
import {
  WorkflowEvent,
  ExecutionResult,
  WorkflowSettings,
  TicketData,
  ClassificationResult,
  FieldChanges,
  ExecutionStepSummary,
  WorkflowRuleWithDetails,
  WorkflowCondition,
  StepName,
  ExecutionStatus,
  StepMethod,
  ConfidenceLevel,
  Branch,
  ClassificationMethod,
} from '../types/workflow';

export class WorkflowEngine {
  private _autotaskClient: AutotaskClient | null = null;

  private getAutotaskClient(): AutotaskClient {
    if (!this._autotaskClient) {
      this._autotaskClient = new AutotaskClient({
        apiUrl: process.env.AUTOTASK_API_URL || '',
        username: process.env.AUTOTASK_USERNAME || '',
        password: process.env.AUTOTASK_SECRET || '',
        apiIntegrationCode: process.env.AUTOTASK_API_INTEGRATION_CODE || '',
      });
    }
    return this._autotaskClient;
  }

  /**
   * Load workflow settings from DB.
   */
  async getSettings(): Promise<WorkflowSettings> {
    const result = await postgresClient.query(
      `SELECT key, value FROM workflow_settings`
    );

    const raw: Record<string, any> = {};
    for (const row of result.rows) {
      try {
        raw[row.key] = JSON.parse(row.value);
      } catch {
        raw[row.key] = row.value;
      }
    }

    return {
      workflow_engine_enabled: raw.workflow_engine_enabled ?? false,
      default_ai_provider: raw.default_ai_provider ?? 'openai',
      openai_api_key: raw.openai_api_key ?? '',
      openai_model: raw.openai_model ?? 'gpt-4o',
      anthropic_api_key: raw.anthropic_api_key ?? '',
      anthropic_model: raw.anthropic_model ?? 'claude-sonnet-4-20250514',
      ai_for_title_cleanup: raw.ai_for_title_cleanup ?? true,
      ai_for_description_rewrite: raw.ai_for_description_rewrite ?? true,
      ai_for_ambiguous_classification: raw.ai_for_ambiguous_classification ?? true,
      ai_for_troubleshooting: raw.ai_for_troubleshooting ?? true,
      autotask_update_delay_ms: Number(raw.autotask_update_delay_ms) || 30000,
      max_ai_retries: Number(raw.max_ai_retries) || 2,
      classification_confidence_threshold: raw.classification_confidence_threshold ?? 'medium',
      log_retention_days: Number(raw.log_retention_days) || 90,
    };
  }

  /**
   * Main entry point: process a workflow event.
   */
  async process(event: WorkflowEvent): Promise<ExecutionResult> {
    const startTime = Date.now();
    const steps: ExecutionStepSummary[] = [];
    const fieldChanges: FieldChanges = {};
    let executionId: number = 0;
    let classificationMethod: ClassificationMethod = 'robotic';
    let branch: Branch = 'service_desk';

    try {
      // 1. Check if engine is enabled
      const settings = await this.getSettings();
      if (!settings.workflow_engine_enabled) {
        console.log('[WORKFLOW] Engine is disabled, skipping');
        return this.buildSkippedResult(0, 'Engine disabled');
      }

      // Create execution record
      executionId = await this.createExecution(event);

      // 2. Load ticket data
      const ticket = event.ticket_data || await this.loadTicketData(event.entity_id);
      if (!ticket) {
        await this.updateExecution(executionId, 'failed', null, null, 'Ticket not found');
        return this.buildSkippedResult(executionId, 'Ticket not found');
      }

      // 3. Run exclusion filter rules
      const filterStep = await this.runFilterRules(ticket, event.trigger_event, executionId);
      steps.push(filterStep);
      if (filterStep.status === 'completed' && filterStep.method === 'skipped') {
        await this.updateExecution(executionId, 'skipped', null, null);
        return {
          execution_id: executionId,
          status: 'skipped',
          classification_method: 'robotic',
          branch: 'service_desk',
          field_changes: {},
          steps,
          duration_ms: Date.now() - startTime,
        };
      }

      // 4. Run robotic classification
      const classifyStart = Date.now();
      const classification = await roboticClassifier.classify(ticket);
      branch = (classification.branch?.value as Branch) || 'service_desk';

      // Log classification steps
      const classificationSteps = this.buildClassificationSteps(classification, executionId, classifyStart);
      steps.push(...classificationSteps);

      // 5. Validate classification
      const validationStep = await this.runValidation(classification, executionId);
      steps.push(validationStep);

      // 6. AI Enhancement (if needed)
      let finalClassification = classification;
      const confidenceThreshold = settings.classification_confidence_threshold;

      if (
        classification.needs_ai &&
        settings.ai_for_ambiguous_classification &&
        this.shouldUseAi(classification.overall_confidence, confidenceThreshold)
      ) {
        classificationMethod = 'hybrid';

        // AI for ambiguous classification
        const failedFields = classification.ai_reasons
          .filter(r => r.includes('No ') && r.includes('matched'))
          .map(r => {
            if (r.includes('ticket type')) return 'ticket_type';
            if (r.includes('issue type')) return 'issue_type';
            if (r.includes('priority')) return 'priority';
            return '';
          })
          .filter(Boolean);

        if (failedFields.length > 0) {
          const aiClassifyStep = await this.runAiClassification(
            ticket, failedFields, finalClassification, settings, executionId
          );
          steps.push(aiClassifyStep);
        }
      }

      // AI for title cleanup
      if (
        settings.ai_for_title_cleanup &&
        classification.ai_reasons.some(r => r.includes('Title'))
      ) {
        classificationMethod = classificationMethod === 'robotic' ? 'hybrid' : classificationMethod;
        const titleStep = await this.runAiTitleCleanup(ticket, settings, executionId);
        steps.push(titleStep);
        if (titleStep.status === 'completed' && (titleStep as any)._newTitle) {
          fieldChanges['title'] = { before: ticket.title, after: (titleStep as any)._newTitle };
        }
      }

      // 7. Build field changes for write-back
      this.buildFieldChanges(ticket, finalClassification, fieldChanges);

      // 8. Delay before Autotask write-back
      if (Object.keys(fieldChanges).length > 0 && settings.autotask_update_delay_ms > 0) {
        await this.delay(settings.autotask_update_delay_ms);
      }

      // 9. Write back to Autotask
      if (Object.keys(fieldChanges).length > 0) {
        const updateStep = await this.writeBackToAutotask(
          ticket.id, fieldChanges, executionId
        );
        steps.push(updateStep);
      }

      // 10. Create troubleshooting note for incidents
      const ticketTypeValue = finalClassification.ticket_type?.value ?? ticket.ticket_type;
      if (
        Number(ticketTypeValue) === 2 &&
        settings.ai_for_troubleshooting
      ) {
        classificationMethod = classificationMethod === 'robotic' ? 'hybrid' : classificationMethod;
        const noteStep = await this.runAiTroubleshooting(ticket, settings, executionId);
        steps.push(noteStep);
      }

      // 11. Update execution record
      const duration = Date.now() - startTime;
      await this.updateExecution(executionId, 'completed', classificationMethod, branch);

      console.log(
        `[WORKFLOW] Completed processing ticket #${ticket.ticket_number} ` +
        `(${classificationMethod}, ${branch}) in ${duration}ms`
      );

      return {
        execution_id: executionId,
        status: 'completed',
        classification_method: classificationMethod,
        branch,
        field_changes: fieldChanges,
        steps,
        duration_ms: duration,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      console.error('[WORKFLOW] Pipeline error:', errorMessage);

      if (executionId) {
        await this.updateExecution(executionId, 'failed', classificationMethod, branch, errorMessage);
      }

      return {
        execution_id: executionId,
        status: 'failed',
        classification_method: classificationMethod,
        branch,
        field_changes: fieldChanges,
        steps,
        duration_ms: Date.now() - startTime,
        error: errorMessage,
      };
    }
  }

  /**
   * Dry-run: process a ticket without writing back to Autotask.
   */
  async dryRun(ticketId: number): Promise<ExecutionResult> {
    const settings = await this.getSettings();
    const ticket = await this.loadTicketData(ticketId);
    if (!ticket) {
      return this.buildSkippedResult(0, 'Ticket not found');
    }

    const startTime = Date.now();
    const steps: ExecutionStepSummary[] = [];

    // Run filter rules
    const filterStep = await this.runFilterRules(ticket, 'ticket.created', 0);
    steps.push(filterStep);

    // Run classification
    const classification = await roboticClassifier.classify(ticket);
    const branch = (classification.branch?.value as Branch) || 'service_desk';
    const classificationSteps = this.buildClassificationSteps(classification, 0, Date.now());
    steps.push(...classificationSteps);

    // Run validation
    const validation = await triageValidator.validate(classification);
    steps.push({
      step_name: 'validation',
      status: validation.is_valid ? 'completed' : 'failed',
      method: 'robotic',
      duration_ms: 0,
    });

    // Build proposed field changes
    const fieldChanges: FieldChanges = {};
    this.buildFieldChanges(ticket, classification, fieldChanges);

    return {
      execution_id: 0,
      status: filterStep.method === 'skipped' ? 'skipped' : 'completed',
      classification_method: 'robotic',
      branch,
      field_changes: fieldChanges,
      steps,
      duration_ms: Date.now() - startTime,
    };
  }

  // ============================================================================
  // Pipeline Steps
  // ============================================================================

  /**
   * Run workflow filter rules (exclusion/inclusion).
   */
  private async runFilterRules(
    ticket: TicketData,
    triggerEvent: string,
    executionId: number
  ): Promise<ExecutionStepSummary> {
    const stepStart = Date.now();

    try {
      const rules = await this.loadWorkflowRules(triggerEvent);

      for (const rule of rules) {
        if (this.evaluateConditions(rule.conditions, ticket)) {
          // Check if any action is 'skip'
          const skipAction = rule.actions.find(a => a.action_type === 'skip');
          if (skipAction) {
            const reason = skipAction.config?.reason || rule.name;
            console.log(`[WORKFLOW] Ticket #${ticket.ticket_number} skipped: ${reason}`);

            if (executionId > 0) {
              await this.logStep(executionId, 'filter', 1, 'completed', 'skipped', {
                rule_name: rule.name,
                reason,
              });
            }

            return {
              step_name: 'filter',
              status: 'completed',
              method: 'skipped',
              matched_rule: rule.name,
              duration_ms: Date.now() - stepStart,
            };
          }

          if (rule.stop_processing) break;
        }
      }

      if (executionId > 0) {
        await this.logStep(executionId, 'filter', 1, 'completed', 'robotic', {
          result: 'passed_all_filters',
        });
      }

      return {
        step_name: 'filter',
        status: 'completed',
        method: 'robotic',
        duration_ms: Date.now() - stepStart,
      };
    } catch (error) {
      console.error('[WORKFLOW] Filter rules error:', error);
      return {
        step_name: 'filter',
        status: 'failed',
        method: 'robotic',
        duration_ms: Date.now() - stepStart,
      };
    }
  }

  /**
   * Build step summaries from classification result.
   */
  private buildClassificationSteps(
    classification: ClassificationResult,
    executionId: number,
    startTime: number
  ): ExecutionStepSummary[] {
    const steps: ExecutionStepSummary[] = [];
    const entries: [StepName, any][] = [
      ['branch_routing', classification.branch],
      ['ticket_type', classification.ticket_type],
      ['issue_classification', classification.issue_classification],
      ['priority', classification.priority],
      ['queue_routing', classification.queue],
    ];

    let order = 2;
    for (const [stepName, result] of entries) {
      steps.push({
        step_name: stepName,
        status: result ? 'completed' : 'completed',
        method: result?.method || 'robotic',
        confidence: result?.confidence,
        matched_rule: result?.matched_rule_name || (result ? undefined : 'No match'),
        duration_ms: 0,
      });

      if (executionId > 0 && result) {
        this.logStep(executionId, stepName, order, 'completed', result.method, {
          field: result.field,
          value: result.value,
          confidence: result.confidence,
          matched_rule: result.matched_rule_name,
        }, result.matched_rule_id).catch(err =>
          console.error(`[WORKFLOW] Failed to log step ${stepName}:`, err)
        );
      }
      order++;
    }

    return steps;
  }

  /**
   * Run validation step.
   */
  private async runValidation(
    classification: ClassificationResult,
    executionId: number
  ): Promise<ExecutionStepSummary> {
    const stepStart = Date.now();
    const validation = await triageValidator.validate(classification);

    if (executionId > 0) {
      await this.logStep(executionId, 'validation', 7, validation.is_valid ? 'completed' : 'failed', 'robotic', {
        is_valid: validation.is_valid,
        errors: validation.errors,
      });
    }

    return {
      step_name: 'validation',
      status: validation.is_valid ? 'completed' : 'failed',
      method: 'robotic',
      duration_ms: Date.now() - stepStart,
    };
  }

  /**
   * Run AI classification for ambiguous fields.
   */
  private async runAiClassification(
    ticket: TicketData,
    failedFields: string[],
    classification: ClassificationResult,
    settings: WorkflowSettings,
    executionId: number
  ): Promise<ExecutionStepSummary> {
    const stepStart = Date.now();

    try {
      const aiResult = await aiTriageService.classifyAmbiguous(ticket, failedFields, settings);

      // Merge AI results into classification
      if (aiResult.classification) {
        if (aiResult.classification.issue_type != null && !classification.issue_classification) {
          classification.issue_classification = {
            field: 'issue_type',
            value: aiResult.classification.issue_type,
            field_2: 'sub_issue_type',
            value_2: aiResult.classification.sub_issue_type,
            confidence: 'medium',
            matched_rule_id: null,
            matched_rule_name: 'AI classification',
            method: 'ai',
          };
        }
        if (aiResult.classification.ticket_type != null && !classification.ticket_type) {
          classification.ticket_type = {
            field: 'ticket_type',
            value: aiResult.classification.ticket_type,
            confidence: 'medium',
            matched_rule_id: null,
            matched_rule_name: 'AI classification',
            method: 'ai',
          };
        }
        if (aiResult.classification.priority != null && !classification.priority) {
          classification.priority = {
            field: 'priority',
            value: aiResult.classification.priority,
            confidence: 'medium',
            matched_rule_id: null,
            matched_rule_name: 'AI classification',
            method: 'ai',
          };
        }
      }

      if (executionId > 0) {
        await this.logStep(executionId, 'ai_classification', 8, 'completed', 'ai', {
          failed_fields: failedFields,
          ai_result: aiResult.classification,
        });
      }

      return {
        step_name: 'ai_classification',
        status: 'completed',
        method: 'ai',
        duration_ms: Date.now() - stepStart,
      };
    } catch (error) {
      console.error('[WORKFLOW] AI classification error:', error);

      if (executionId > 0) {
        await this.logStep(executionId, 'ai_classification', 8, 'failed', 'ai', {
          error: error instanceof Error ? error.message : String(error),
        });
      }

      return {
        step_name: 'ai_classification',
        status: 'failed',
        method: 'ai',
        duration_ms: Date.now() - stepStart,
      };
    }
  }

  /**
   * Run AI title cleanup.
   */
  private async runAiTitleCleanup(
    ticket: TicketData,
    settings: WorkflowSettings,
    executionId: number
  ): Promise<ExecutionStepSummary & { _newTitle?: string }> {
    const stepStart = Date.now();

    try {
      const newTitle = await aiTriageService.cleanupTitle(ticket, settings);

      if (executionId > 0) {
        await this.logStep(executionId, 'ai_title', 9, 'completed', 'ai', {
          original: ticket.title,
          cleaned: newTitle,
        });
      }

      return {
        step_name: 'ai_title',
        status: 'completed',
        method: 'ai',
        duration_ms: Date.now() - stepStart,
        _newTitle: newTitle,
      };
    } catch (error) {
      console.error('[WORKFLOW] AI title cleanup error:', error);
      return {
        step_name: 'ai_title',
        status: 'failed',
        method: 'ai',
        duration_ms: Date.now() - stepStart,
      };
    }
  }

  /**
   * Run AI troubleshooting steps generation and create a ticket note.
   */
  private async runAiTroubleshooting(
    ticket: TicketData,
    settings: WorkflowSettings,
    executionId: number
  ): Promise<ExecutionStepSummary> {
    const stepStart = Date.now();

    try {
      const steps = await aiTriageService.generateTroubleshootingSteps(ticket, settings);
      if (!steps) {
        return {
          step_name: 'ai_troubleshooting',
          status: 'completed',
          method: 'skipped',
          duration_ms: Date.now() - stepStart,
        };
      }

      // Create ticket note in Autotask
      const client = this.getAutotaskClient();
      await client.createEntity('TicketNotes', {
        ticketID: ticket.id,
        title: 'Troubleshooting Steps (Auto-Generated)',
        description: steps,
        noteType: 1, // Internal
        publish: 1,
      });

      if (executionId > 0) {
        await this.logStep(executionId, 'create_note', 12, 'completed', 'ai', {
          note_type: 'troubleshooting_steps',
          content_length: steps.length,
        });
      }

      return {
        step_name: 'create_note',
        status: 'completed',
        method: 'ai',
        duration_ms: Date.now() - stepStart,
      };
    } catch (error) {
      console.error('[WORKFLOW] Troubleshooting note error:', error);
      return {
        step_name: 'create_note',
        status: 'failed',
        method: 'ai',
        duration_ms: Date.now() - stepStart,
      };
    }
  }

  /**
   * Write classified fields back to Autotask.
   */
  private async writeBackToAutotask(
    ticketId: number,
    fieldChanges: FieldChanges,
    executionId: number
  ): Promise<ExecutionStepSummary> {
    const stepStart = Date.now();

    try {
      // Build Autotask update payload (camelCase field names)
      const updatePayload: Record<string, any> = { id: ticketId };
      const fieldMap: Record<string, string> = {
        ticket_type: 'ticketType',
        issue_type: 'issueType',
        sub_issue_type: 'subIssueType',
        priority: 'priority',
        queue_id: 'queueID',
        title: 'title',
      };

      for (const [field, change] of Object.entries(fieldChanges)) {
        const autotaskField = fieldMap[field] || field;
        updatePayload[autotaskField] = change.after;
      }

      const client = this.getAutotaskClient();
      await client.updateTicket(ticketId, updatePayload);

      // Also update local DB
      const dbUpdates: Record<string, any> = {};
      for (const [field, change] of Object.entries(fieldChanges)) {
        dbUpdates[field] = change.after;
      }
      if (Object.keys(dbUpdates).length > 0) {
        const setClauses = Object.keys(dbUpdates).map((k, i) => `${k} = $${i + 2}`);
        await postgresClient.query(
          `UPDATE tickets SET ${setClauses.join(', ')}, updated_at = NOW() WHERE id = $1`,
          [ticketId, ...Object.values(dbUpdates)]
        );
      }

      if (executionId > 0) {
        await this.logStep(executionId, 'autotask_update', 11, 'completed', 'robotic', {
          fields_updated: Object.keys(fieldChanges),
        });
      }

      console.log(`[WORKFLOW] Updated ticket ${ticketId} in Autotask: ${Object.keys(fieldChanges).join(', ')}`);

      return {
        step_name: 'autotask_update',
        status: 'completed',
        method: 'robotic',
        duration_ms: Date.now() - stepStart,
      };
    } catch (error) {
      console.error('[WORKFLOW] Autotask update error:', error);

      if (executionId > 0) {
        await this.logStep(executionId, 'autotask_update', 11, 'failed', 'robotic', {
          error: error instanceof Error ? error.message : String(error),
        });
      }

      return {
        step_name: 'autotask_update',
        status: 'failed',
        method: 'robotic',
        duration_ms: Date.now() - stepStart,
      };
    }
  }

  // ============================================================================
  // Helpers
  // ============================================================================

  /**
   * Load ticket data from DB.
   */
  private async loadTicketData(ticketId: number): Promise<TicketData | null> {
    const result = await postgresClient.query(
      `SELECT id, ticket_number, title, description, ticket_category, ticket_type,
              priority, queue_id, issue_type, sub_issue_type, company_id, contact_id,
              assigned_resource_id, status, source
       FROM tickets WHERE id = $1`,
      [ticketId]
    );

    if (result.rows.length === 0) return null;

    const row = result.rows[0];
    return {
      ...row,
      // Try to extract device/policy info from title/description for queue routing
      device_name: this.extractDeviceName(row.title, row.description),
      policy_name: this.extractPolicyName(row.description),
      creator_resource_id: null, // Not stored on tickets table — comes from webhook
      person_id: null,
    };
  }

  /**
   * Extract device name patterns from text (e.g., DT-1234, LT-5678).
   */
  private extractDeviceName(title: string | null, description: string | null): string | null {
    const text = [title, description].filter(Boolean).join(' ');
    const match = text.match(/\b(DT|LT|SP|SRV|VM)-?\d{2,}/i);
    return match ? match[0] : null;
  }

  /**
   * Extract policy name from description (e.g., "Policy: Windows Workstation").
   */
  private extractPolicyName(description: string | null): string | null {
    if (!description) return null;
    const match = description.match(/(?:policy|monitoring policy)[:\s]+([^\n]+)/i);
    return match ? match[1].trim() : null;
  }

  /**
   * Load workflow filter rules with conditions and actions.
   */
  private async loadWorkflowRules(triggerEvent: string): Promise<WorkflowRuleWithDetails[]> {
    const rulesResult = await postgresClient.query(
      `SELECT * FROM workflow_rules WHERE is_active = true AND trigger_event = $1 ORDER BY sort_order`,
      [triggerEvent]
    );

    const rules: WorkflowRuleWithDetails[] = [];
    for (const rule of rulesResult.rows) {
      const [conditionsResult, actionsResult] = await Promise.all([
        postgresClient.query(`SELECT * FROM workflow_conditions WHERE rule_id = $1`, [rule.id]),
        postgresClient.query(`SELECT * FROM workflow_actions WHERE rule_id = $1 ORDER BY sort_order`, [rule.id]),
      ]);

      rules.push({
        ...rule,
        conditions: conditionsResult.rows,
        actions: actionsResult.rows,
      });
    }

    return rules;
  }

  /**
   * Evaluate workflow conditions against ticket data.
   * AND within condition_group, OR between groups.
   */
  private evaluateConditions(conditions: WorkflowCondition[], ticket: TicketData): boolean {
    if (conditions.length === 0) return true;

    // Group conditions by condition_group
    const groups = new Map<number, WorkflowCondition[]>();
    for (const cond of conditions) {
      const group = groups.get(cond.condition_group) || [];
      group.push(cond);
      groups.set(cond.condition_group, group);
    }

    // OR between groups: at least one group must pass
    for (const [, groupConditions] of groups) {
      const groupPasses = groupConditions.every(cond => this.evaluateCondition(cond, ticket));
      if (groupPasses) return true;
    }

    return false;
  }

  /**
   * Evaluate a single workflow condition.
   */
  private evaluateCondition(condition: WorkflowCondition, ticket: TicketData): boolean {
    const fieldValue = this.getTicketFieldValue(condition.field, ticket);

    switch (condition.operator) {
      case 'equals':
        return this.isEqual(fieldValue, condition.value);
      case 'not_equals':
        return !this.isEqual(fieldValue, condition.value);
      case 'in':
        return this.isIn(fieldValue, condition.value);
      case 'not_in':
        return !this.isIn(fieldValue, condition.value);
      case 'contains':
        return fieldValue != null && String(fieldValue).toLowerCase().includes(String(condition.value).toLowerCase());
      case 'not_contains':
        return fieldValue == null || !String(fieldValue).toLowerCase().includes(String(condition.value).toLowerCase());
      case 'regex':
        try {
          return fieldValue != null && new RegExp(String(condition.value), 'i').test(String(fieldValue));
        } catch { return false; }
      case 'gt':
        return fieldValue != null && Number(fieldValue) > Number(condition.value);
      case 'lt':
        return fieldValue != null && Number(fieldValue) < Number(condition.value);
      case 'is_null':
        return fieldValue == null;
      case 'is_not_null':
        return fieldValue != null;
      default:
        return false;
    }
  }

  private getTicketFieldValue(field: string, ticket: TicketData): any {
    return (ticket as any)[field] ?? null;
  }

  private isEqual(a: any, b: any): boolean {
    if (a == null && b == null) return true;
    if (a == null || b == null) return false;
    return String(a) === String(b);
  }

  private isIn(value: any, list: any): boolean {
    if (value == null) return false;
    const arr = Array.isArray(list) ? list : [list];
    return arr.some(v => String(v) === String(value));
  }

  /**
   * Build field changes from classification result.
   */
  private buildFieldChanges(
    ticket: TicketData,
    classification: ClassificationResult,
    fieldChanges: FieldChanges
  ): void {
    if (classification.ticket_type?.value != null && classification.ticket_type.value !== ticket.ticket_type) {
      fieldChanges['ticket_type'] = { before: ticket.ticket_type, after: Number(classification.ticket_type.value) };
    }

    if (classification.issue_classification?.value != null && Number(classification.issue_classification.value) !== ticket.issue_type) {
      fieldChanges['issue_type'] = { before: ticket.issue_type, after: Number(classification.issue_classification.value) };
    }

    if (classification.issue_classification?.value_2 != null && Number(classification.issue_classification.value_2) !== ticket.sub_issue_type) {
      fieldChanges['sub_issue_type'] = { before: ticket.sub_issue_type, after: Number(classification.issue_classification.value_2) };
    }

    if (classification.priority?.value != null && Number(classification.priority.value) !== ticket.priority) {
      fieldChanges['priority'] = { before: ticket.priority, after: Number(classification.priority.value) };
    }

    if (classification.queue?.value != null && Number(classification.queue.value) !== ticket.queue_id) {
      fieldChanges['queue_id'] = { before: ticket.queue_id, after: Number(classification.queue.value) };
    }
  }

  /**
   * Determine if AI should be used based on confidence.
   */
  private shouldUseAi(confidence: ConfidenceLevel, threshold: ConfidenceLevel): boolean {
    const levels: Record<ConfidenceLevel, number> = { high: 3, medium: 2, low: 1 };
    return levels[confidence] < levels[threshold];
  }

  /**
   * Create a workflow execution record.
   */
  private async createExecution(event: WorkflowEvent): Promise<number> {
    const result = await postgresClient.query(
      `INSERT INTO workflow_executions (trigger_event, entity_type, entity_id, ticket_number, status)
       VALUES ($1, $2, $3, $4, 'running')
       RETURNING id`,
      [event.trigger_event, event.entity_type, event.entity_id, event.ticket_number || null]
    );
    return result.rows[0].id;
  }

  /**
   * Update execution status.
   */
  private async updateExecution(
    executionId: number,
    status: ExecutionStatus,
    method: ClassificationMethod | null,
    branch: Branch | null,
    errorMessage?: string
  ): Promise<void> {
    await postgresClient.query(
      `UPDATE workflow_executions
       SET status = $1, classification_method = $2, branch = $3,
           completed_at = NOW(), duration_ms = EXTRACT(EPOCH FROM (NOW() - started_at)) * 1000,
           error_message = $4
       WHERE id = $5`,
      [status, method, branch, errorMessage || null, executionId]
    );
  }

  /**
   * Log an execution step.
   */
  private async logStep(
    executionId: number,
    stepName: string,
    stepOrder: number,
    status: ExecutionStatus,
    method: StepMethod,
    outputData?: Record<string, any>,
    classificationRuleId?: number | null
  ): Promise<void> {
    await postgresClient.query(
      `INSERT INTO workflow_execution_steps
       (execution_id, step_name, step_order, status, method, output_data, classification_rule_id, completed_at)
       VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`,
      [executionId, stepName, stepOrder, status, method, outputData ? JSON.stringify(outputData) : null, classificationRuleId || null]
    );
  }

  /**
   * Build a skipped execution result.
   */
  private buildSkippedResult(executionId: number, reason: string): ExecutionResult {
    return {
      execution_id: executionId,
      status: 'skipped',
      classification_method: 'robotic',
      branch: 'service_desk',
      field_changes: {},
      steps: [],
      duration_ms: 0,
      error: reason,
    };
  }

  /**
   * Delay for a specified number of milliseconds.
   */
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Export singleton instance
export const workflowEngine = new WorkflowEngine();
