/**
 * Delay Scheduler
 *
 * Manages delayed workflow instances using Redis sorted sets.
 * Instances scheduled for future execution are stored with their resume time.
 */

import { getModels } from '../../models';
import type { ScheduledDelay } from './types';

// Redis key for delayed instances
const DELAY_QUEUE_KEY = 'automation:delayed_instances';

// Mock Redis for development (replace with actual Redis client)
class MockRedis {
  private data: Map<string, { score: number; member: string }[]> = new Map();

  async zadd(key: string, ...members: { score: number; member: string }[]): Promise<number> {
    if (!this.data.has(key)) {
      this.data.set(key, []);
    }
    const set = this.data.get(key)!;
    for (const m of members) {
      const existing = set.findIndex((item) => item.member === m.member);
      if (existing >= 0) {
        set[existing] = m;
      } else {
        set.push(m);
      }
    }
    return members.length;
  }

  async zrangebyscore(key: string, min: number, max: number): Promise<string[]> {
    const set = this.data.get(key) || [];
    return set
      .filter((item) => item.score >= min && item.score <= max)
      .sort((a, b) => a.score - b.score)
      .map((item) => item.member);
  }

  async zremrangebyscore(key: string, min: number, max: number): Promise<number> {
    const set = this.data.get(key) || [];
    const initialLength = set.length;
    const filtered = set.filter((item) => item.score < min || item.score > max);
    this.data.set(key, filtered);
    return initialLength - filtered.length;
  }

  async zrem(key: string, ...members: string[]): Promise<number> {
    const set = this.data.get(key) || [];
    const initialLength = set.length;
    const filtered = set.filter((item) => !members.includes(item.member));
    this.data.set(key, filtered);
    return initialLength - filtered.length;
  }
}

// Global mock Redis instance
let redisClient: MockRedis | any = null;

/**
 * Get Redis client (or mock for development)
 */
function getRedisClient(): MockRedis {
  if (!redisClient) {
    // Try to use real Redis if available
    try {
      // Check if Redis is configured
      if (process.env.REDIS_URL) {
        // Use actual Redis client
        // const Redis = require('ioredis');
        // redisClient = new Redis(process.env.REDIS_URL);
        // For now, use mock
        redisClient = new MockRedis();
      } else {
        // Use mock Redis
        redisClient = new MockRedis();
      }
    } catch (error) {
      console.warn('[DelayScheduler] Redis not available, using mock');
      redisClient = new MockRedis();
    }
  }
  return redisClient;
}

// ============================================
// DELAY SCHEDULER CLASS
// ============================================

export class DelayScheduler {
  /**
   * Schedule an instance for later processing
   */
  async scheduleInstance(instanceId: string, delayUntil: Date): Promise<void> {
    const { AutomationInstance } = getModels();
    const redis = getRedisClient();

    try {
      // Add to Redis sorted set with timestamp as score
      const score = delayUntil.getTime();
      await redis.zadd(DELAY_QUEUE_KEY, { score, member: instanceId });

      // Update instance status
      await AutomationInstance.findByIdAndUpdate(instanceId, {
        $set: {
          status: 'paused',
          scheduledFor: delayUntil,
          lastProcessedAt: new Date(),
        },
      });

      console.log(`[DelayScheduler] Scheduled instance ${instanceId} for ${delayUntil.toISOString()}`);
    } catch (error) {
      console.error('[DelayScheduler] Error scheduling instance:', error);
      throw error;
    }
  }

  /**
   * Get instances ready to process (scheduled time has passed)
   */
  async getDueInstances(): Promise<string[]> {
    const redis = getRedisClient();

    try {
      const now = Date.now();

      // Get all instances with scheduledFor <= now
      const instanceIds = await redis.zrangebyscore(DELAY_QUEUE_KEY, 0, now);

      if (instanceIds.length > 0) {
        // Remove them from the sorted set
        await redis.zremrangebyscore(DELAY_QUEUE_KEY, 0, now);
      }

      return instanceIds;
    } catch (error) {
      console.error('[DelayScheduler] Error getting due instances:', error);
      return [];
    }
  }

  /**
   * Cancel a scheduled instance
   */
  async cancelScheduledInstance(instanceId: string): Promise<boolean> {
    const redis = getRedisClient();

    try {
      const result = await redis.zrem(DELAY_QUEUE_KEY, instanceId);
      return result > 0;
    } catch (error) {
      console.error('[DelayScheduler] Error canceling scheduled instance:', error);
      return false;
    }
  }

  /**
   * Get count of scheduled instances
   */
  async getScheduledCount(): Promise<number> {
    const redis = getRedisClient();

    try {
      // Get all instances (any score)
      const all = await redis.zrangebyscore(DELAY_QUEUE_KEY, 0, Infinity);
      return all.length;
    } catch (error) {
      console.error('[DelayScheduler] Error getting scheduled count:', error);
      return 0;
    }
  }

  /**
   * Get scheduled instances for a specific workflow
   */
  async getScheduledForWorkflow(workflowId: string): Promise<ScheduledDelay[]> {
    const { AutomationInstance } = getModels();
    const redis = getRedisClient();

    try {
      // Get all scheduled instance IDs
      const instanceIds = await redis.zrangebyscore(DELAY_QUEUE_KEY, 0, Infinity);

      if (instanceIds.length === 0) {
        return [];
      }

      // Fetch instance details
      const instances = await AutomationInstance.find({
        _id: { $in: instanceIds },
        workflowId,
      })
        .select('_id workflowId scheduledFor')
        .lean();

      return instances.map((inst: any) => ({
        instanceId: inst._id.toString(),
        workflowId: inst.workflowId.toString(),
        companyId: inst.companyId,
        nodeId: inst.currentNodeId,
        scheduledFor: inst.scheduledFor!,
        createdAt: inst.createdAt,
      }));
    } catch (error) {
      console.error('[DelayScheduler] Error getting scheduled for workflow:', error);
      return [];
    }
  }

  /**
   * Reschedule an instance (update delay time)
   */
  async rescheduleInstance(instanceId: string, newDelayUntil: Date): Promise<void> {
    const { AutomationInstance } = getModels();
    const redis = getRedisClient();

    try {
      // Update score in Redis
      const score = newDelayUntil.getTime();
      await redis.zadd(DELAY_QUEUE_KEY, { score, member: instanceId });

      // Update instance
      await AutomationInstance.findByIdAndUpdate(instanceId, {
        $set: {
          scheduledFor: newDelayUntil,
        },
      });

      console.log(`[DelayScheduler] Rescheduled instance ${instanceId} for ${newDelayUntil.toISOString()}`);
    } catch (error) {
      console.error('[DelayScheduler] Error rescheduling instance:', error);
      throw error;
    }
  }
}